Experimental Products: SparkX products are rapidly produced to bring you the most cutting edge technology as it becomes available. These products are tested but come with no guarantees. Live technical support is not available for SparkX products.
An 8-channel, 24MHz USB logic analyzer for under $20. What a deal!
But a USB logic analyzer (LA) is only as useful as the software required to configure and monitor the tool. There are a few software options available for this USB logic analyzer; in this tutorial we aim to familiarize you with sigrok’s PulseView.
sigrok is an open-source suite of software projects – all focused on supporting signal analysis tools. The project includes:
PulseView– A logic analyzer front end with a simple GUI.
sigrok-cli– A command line interface for sigrok – useful for scripting tests or running on a headless machine.
fx2grok– A collection of open-source hardware LA layouts, schematics, and BOM’s.
With an eye towards logic analyzers, this tutorial will focus mostly on PulseView.
Get the Software
Download the latest PulseView release from sigrok’s Downloads page. Here are direct links for the latest Windows, Mac, and Linux downloads:
Windows users can run the installer executable (pulseview-NIGHTLY-32bit-static-release-installer.exe) to install the software on your machine. The Mac installer is a binary disk image (DMG), which can be dragged into your Applications folder, for example.
Once installed open PulseView – the GUI frontend for sigrok.
Setting up the Software/Hardware
With PulseView open, plug in your USB Logic Analyzer. You should see faint red and green LEDs illuminate under the sticker.
If PulseView does not automatically detect your logic analyzer, you’ll need to set it manually:
Click the “<No Device>” dropdown menu.
Select fx2lafw (generic driver for FX2 based LAs) from the dropdown.
Select USB for the interface
Click Scan for devices using driver above
Select “Saleae Logic with 8 channels” and click “OK”
You’ll be greeted with a blank slate of eight colorful bands of logic channels, numbered D0 to D7 (these match the CH0-CH7 labels on the LA).
Click the Run button in the top-left of the window to beginning scanning.
With the sampling parameters set as default – 1M samples, 20kHz – it’ll take almost a minute to gather all million samples. You can hit “Stop” to end the scan early.
Unless you’ve already connected a few channels and grounds, this first scan will probably not be that interesting.
Exploring the Capabilities
Here’s a fun, tormenting Arduino sketch you can load to help familiarize yourself with PulseView’s capabilities:
language:c
void setup() {
// put your setup code here, to run once:
randomSeed(analogRead(A0));
Serial.begin(random(1, 115200)); // Set the baud rate to a random value between 1 and 115200 bps
}
void loop() {
Serial.println(millis()); // Print the time
delay(250);
}
Load that onto an Arduino, then connect the logic analyzers “CH0” to your Arduino’s TX pin (pin 1). Also connect on of the GND wires to GND.
Before scanning, bump up the sample rate to 1MHz and change the sample quantity to 1 M samples. Depending on what you’re trying to analyze, these dropdowns may get a lot of use. With those values set, hit Run.
You should be greeted with a seconds worth of samples, and a few blips on the D0 channel every 250ms.
You can use your mouse’s scroll wheel to zoom in and out, or use the “+” and “-” buttons on the toolbar. Zoom into one of the blips. Now it’s your job to guess the baud rate!
The Show cursors (pair of blue flags icon) tool can be useful for measuring time. Click that, then try to place the cursors around one bit of the transmission. The measured frequency should be our mystery baud rate!
To decode the string, use the Add low-level, non-stacked protocol decoder tool (looks like yellow and green decoded signals). Then select UART– note that a huge list of protocols pops up here, including I2C, I2S, SDIO, and SPI.
Click the green “UART” icon that appears towards the bottom-left and change the baud rate to your measured frequency. You can also change the data format to ascii to make the data easier to parse.
Now if you zoom out you should see your serial prints decoded!
CLI and Going Further
If you’re connecting the logic analyzer to a headless machine, or want to automate a LA-based test, check out sigrok-cli– a command line interface for sigrok. With sigrok-cli installed, for example, you can use a command like:
The CLI has a lot of potential for automation, and the man page is super-helpful!
As you venture into this world of logic analyzing, be sure to try out all of PuseView’s protocol decoders and features. It’s a great software tool and has a powerful open-source community behind it.
Experimental Products: SparkX products are rapidly produced to bring you the most cutting edge technology as it becomes available. These products are tested but come with no guarantees. Live technical support is not available for SparkX products.
Arduino and Arduino compatible dev boards are an awesome tool for developing an idea quickly but, being development boards, they’re often a little more bulky and full featured than you really need. Having a USB interface and a bootloader is so nice, though, so we put together the bare minimum Arduino compatible breakout for integration into your small projects. We call it the Atto84.
The Atto84 is essentially a breakout board for the absolutely minute WQFN ATtiny84, but we’ve done some work to make it easier to program. First off, we’ve added a micro-USB connector a firmware-based USB driver for the ATtiny that allows you to program the chip over USB. In addition, we’ve created an Arduino board profile that combines this bootloader with an extremely full-featured ATtiny Arduino core.
Simply install the USB drivers on your computer, select the board profile from Arduino’s Board Manager and upload code to this board like any other Arduino style development board.
In this hookup guide, you’ll learn how to do exactly that!
Installing USB Drivers
The Atto84 is emulating USB 1.1 using two of its pins and the V-USB driver. However, there are no common operating system drivers available that work with this custom USB class. As a result, we will need to install custom drivers in order to communicate with (and send our Arduino programs to) the Atto84. Choose your operating system below and follow the directions to install the driver.
Note: We did not write the USB firmware nor the driver. We simply packaged and modified them to work with the Atto84. The true geniuses are the fine folks who wrote micronucleus and libusb.
Windows
Insert a micro-USB cable into the Atto84. Your PC will probably make a happy “USB connected!” chime and then inform you that there is an unknown device connected.
Download the SparkFun ATtiny USB drivers by clicking on the link below.
Unzip the file. Open the Windows Device Manager, and you should see an Unknown device. Right-click on Unknown device and select Update Driver Software.
In the pop-up window, click Browse my computer for driver software.
Click Browse… and open the folder that contains the drivers you just unzipped. It will likely be the sparkfun_attiny_usb_driver folder.
Click Next. You may get a warning pop-up that says “Windows can’t verify the publisher of this driver software.” That’s OK. Just click Install the driver software anyway.
You should see a notification that the SparkFun ATtiny driver was installed successfully. Close that window, and verify that your Unknown device now shows up as SparkFun ATtiny in the Device Manager.
MacOS
You’ll need to install Homebrew and use it to install libusb. Enter the following commands into a Terminal:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew doctor
brew install libusb-compat
Linux
Good news! Linux doesn’t require special drivers. However, you will need to do one of the following to be able to program the Atto84 from Arduino:
1) When you download the Arduino IDE (next section), make sure you run it as root: sudo ./arduino
2) Or, you can add some udev rules so that Linux enumerates your device with write permissions. Create a file in rules.d:
sudo edit /etc/udev/rules.d/49-micronucleus.rules
Copy the following contents into that file:
# UDEV Rules for Micronucleus boards including the Digispark.
# This file must be placed at:
#
# /etc/udev/rules.d/49-micronucleus.rules (preferred location)
# or
# /lib/udev/rules.d/49-micronucleus.rules (req'd on some broken systems)
#
# After this file is copied, physically unplug and reconnect the board.
#
SUBSYSTEMS=="usb", ATTRS{idVendor}=="16d0", ATTRS{idProduct}=="0753", MODE:="0666"
KERNEL=="ttyACM*", ATTRS{idVendor}=="16d0", ATTRS{idProduct}=="0753", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1"
#
# If you share your linux system with other users, or just don't like the
# idea of write permission for everybody, you can replace MODE:="0666" with
# OWNER:="yourusername" to create the device owned by you, or with
# GROUP:="somegroupname" and mange access using standard unix groups.
Save and exit.
Installing the Board Package
If you don’t have the Arduino IDE on your computer, you’ll want to get that installed first.
If you need help installing Arduino for your operating system, you can follow this guide.
Important! Your Atto84 will only work with Arduino versions 1.6.10 and above.
Download and Install the Board Package
Because the Atto84 is not supported by the Arduino IDE by default, we need to add it manually. Open the Arduino program and go to File > Preferences. Then copy and paste the URL below into the Additional Board Manager URLs text box.
If you’re thinking “I already have the SparkFun Board Profiles and I don’t see any tiny boards!” notice that we’re specifically pointing to a branch of SparkFun’s Arduino Boards repository called “tiny,” not the main branch. If you copy/paste the URL below, you should have no troubles!
Then hit OK, and navigate to the Tools > Board > Boards Manager… tool. A search for “tiny” should turn up a SparkFun ATtiny Boards result. Select that and click Install.
Once the installation is complete, go to Tools > Board and select SparkX Atto84 (ATtiny84, 3.3V, 8MHz) under the SparkFun ATtiny Boards section.
Tiny Tricks and Gotchas
Because the Atto84 is the bare-minimum of hardware that can be USB programmed, it does have a few quirks that you should be aware of. But first, let’s talk about a few of the cool features that it inherits from SpenceKonde’s ATTiny Core.
I²C Support
The ATTiny Core includes a special version of the Wire library that leverages the tiny84’s hardware USI for I²C communication. This means that it will run any Wire-based code that you’ve written for ATmega platforms like the Arduino Uno or the SparkFun RedBoard.
SPI Support
The ATtiny actually has hardware SPI support, so the Atto84 will work identically to the Uno in that respect.
Servos?
Yeah, why not? The tiny84 has plenty of timers and you’ll find a copy of the Servo library included with the core!
The “Gotchas”
Sounds great, right? It is, but there are a few catches that you should be aware of.
Power
There is no power regulator on the Atto84, so the input to VCC must be between 3.3V and 5V. Powering your project through the USB connector is a good, too.
Serial
The ATtiny doesn’t have a hardware UART, so serial doesn’t work the way you might expect. There is a custom library built in to the ATTiny Core called “Serial” which is actually an implementation of Software Serial. This works quite well but you do need to know its limitations. For instance, it’s not full duplex, so sending and receiving at the same time will produce gibberish. Also, it does not implement V_USB, so if you want to do Serial debugging, you’ll need to connect a separate USB-Serial adapter.
Uploading Code
Most USB Arduino boards can be forced into bootloader mode by the Arduino IDE during programming, but due to the limitations of V-USB, the Atto84 needs to me manually reset during programming. Every time the Atto84 restarts, it goes into bootloader mode for about 5 seconds. During this time, your computer will recognize it as a USB device. After 5 seconds, the Atto84 begins running user code. Unless your code also happens to be implementing V-USB, the computer will stop recognizing the device.
In order to get code onto the board, simply click the upload button in the Arduino IDE and wait for the “uploading…” message to show. When the IDE says “uploading,” press the reset button on your Atto84. Don’t worry, the timing isn’t critical, the Micronucleus upload program gives you 30 seconds to press reset. After the board has reset and shows up as a USB device, your code will upload like normal!
Note: If you get an error message while uploading, it could be caused by a variety of reasons. The way we're uploading programs to Roshamglo is actually hacked together, as we're emulating USB on the badge, which many computers do not like. Here are some things to try if you do get an error:
The Raspberry Pi is an amazing single board computer (SBC) capable of running Linux and a whole host of applications. Python is a beginner-friendly programming language that is used in schools, web development, scientific research, and in many other industries. This guide will walk you through writing your own programs with Python to blink lights, respond to button pushes, read sensors, and log data on the Raspberry Pi.
Notice: This tutorial was written with Raspbian version "April 2018" and Python version 3.5.3. Other versions may affect how some of the steps in this guide are performed.
Required Materials
To work through the activities in this tutorial, you will need a few pieces of hardware:
Optional Materials
You have several options when it comes to working with the Raspberry Pi. Most commonly, the Pi is used as a standalone computer, which requires a monitor, keyboard, and mouse (listed below). To save on costs, the Pi can also be used as a headless computer (without a monitor, keyboard, and mouse). This setup has a slightly more difficult learning curve, as you will need to use the command-line interface (CLI) from another computer.
You have a few options when it comes to interacting with the Raspberry Pi. The first and most common is to use it like you would a full desktop computer (just smaller). This involves connecting a keyboard, mouse, and monitor. With this setup, you are likely best served by installing Raspbian with Desktop, which gives you a full graphical user interface (GUI) to work with. This is the best option if you want an experience similar to working with other operating systems (OS), such as Windows, macOS, or other popular Linux flavors, like Ubuntu.
Option 1: Use the Raspberry Pi like a full computer with keyboard, mouse, and monitor
The other option is to create a headless setup, which means you can skip the monitor, keyboard, and mouse. While this is the cheaper way to go, it means you’ll need to be open to performing all your actions in the command line interface. For this, you will want either Raspbian with Desktop or Raspbian Lite operating systems.
Option 2: Configure the Raspberry Pi for “headless” operation where you interact with it from another computer
This guide will show you how to write and run Python programs that will work in both configurations.
Option 1: Full Desktop Setup
The Raspberry Pi 3 Starter Kit Hookup Guide offers a great walkthrough to setting up your Raspberry Pi with NOOBS (Raspberry Pi’s easy-to-use graphical OS installer).
From here on out, all instructions that are unique to the full desktop setup will be highlighted in blue.
If you want to skip the keyboard, mouse, and monitor, you can install Raspbian Lite. This will allow you to get a terminal into your Pi using SSH or Serial on another computer. The Headless Raspberry Pi Setup walks you through setting up your Raspberry Pi without a graphical interface.
Instructions in this tutorial that are unique to the headless setup will be highlighted in yellow.
If you have the Raspberry Pi Starter Kit, you can attach the Pi Wedge to the Pi over the provided ribbon cable, and connect the FTDI Breakout board to the Pi Wedge. From here, connect a USB cable between your computer and the FTDI Breakout board. This will allow you to open a Serial terminal to your Raspberry Pi, as covered in the Serial Terminal section of the Headless Pi tutorial.
Configure Your Pi
Regardless of whether you are using the full desktop or a headless setup, you will need to perform some basic configuration steps on your new Raspberry Pi installation. These steps can be easily performs from a terminal (a text input/output environment).
Full Desktop: You should be automatically logged into the X windows manager (otherwise known as the desktop). To open a terminal, simply click on the Terminal icon on the top left of the desktop. You should be immediately presented with a command prompt in a terminal window.
Headless: With a headless setup, everything you do will be through a terminal. When you connect to your Pi through Serial or SSH, you will be presented with a login prompt on the terminal. Enter the default credentials:
Username: pi
Password: raspberry
You will be presented with a command prompt.
Run the Config Tool
From your command prompt, enter the command:
sudo raspi-config
If asked to enter a password, type out the default password: raspberry.
You will be given several options on how to configure your Raspberry Pi.
Use the arrow keys to select 1 Change User Password and follow the on-screen prompts to change your default password.
Warning: It is strongly recommended that you change your password. If you connect your Pi to a network and leave the password as 'raspberry', anyone with access to that network will be able to easily get into your Pi.
Next, select 2 Network Options
In the following screen, select N2 Wi-fi, and follow the prompts to connect your Pi to a local WiFi network (assuming you have one available).
Select 4 Localisation Options to bring up the keyboard and time zone options.
Select I1 Change Local
Scroll down to highlight en_GB.UTF-8 UTF-8, and press the spacebar to deselect it (the asterisk ‘*’ will disappear)
Scroll to find your language/country and press space to select it (an asterisk ‘*’ will appear next to your selection)
If you live in Great Britain, you can just leave en_GB.UTF-8 UTF-8 selected
If you live in the United States, you will probably want to select en_US.UTF-8 UTF-8.
Press enter to save the changes
On the next screen, highlight your chosen localization option (e.g. en_US.UTF-8 if you’re in the United States) and press enter.
Go back into the 4 Localisations Options, and select I2 Change Timezone
Follow the prompts to select your timezone.
Back in 4 Localisations Options, select I3 Change Keyboard Layout
Choose your preferred layout (the default Generic 105-key (Intl) PC seems to work well in most situations)
On the next screen, select the layout for your language/country
If you live in Great Britain, you can leave English (UK) selected. Otherwise, select Other, press enter, and follow the prompts to select your language/country. If you live in the United States, select English (US), and on the next screen, scroll up to highlight English (US). Press enter.
Leave The default for the keyboard layout selected, and press enter
Once again, leave the default No compose key selected, and press enter
Leave No selected when asked about using Control+Alt+Backspace, and press enter
After a few moments, you will be dropped back into the main Configuration Tool menu.
Select 5 Interfacing Options.
Feel free to enable the Camera interface and SSH if you think you’ll need them
Select SPI, select yes on the following screen, press enter
Repeat for I2C
Repeat for Serial
Back in the main screen, select 7 Advanced Options.
Select A1 Expand Filesystem, and press enter
Go back into 7 Advanced Options, select A4 Audio, highlight 1 Force 3.5mm (‘headphone’) jack, and press enter
Use the right arrow key to select Finish, and press enter. If asked to reboot, select Yes and press enter. Wait while your Raspberry Pi restarts.
If you are using a Serial or SSH terminal, log back in using the username pi and the password you created earlier.
Use Python 3
By default, Raspbian (Stretch version April 2018 and earlier) uses Python 2. However, versions 2 and 3 come installed by default. We just have to make 1 minor change so that the Pi uses Python 3 whenever we type python into a terminal.
In a terminal window, enter the following command:
python --version
You should see which version is being used by default. For example, you might see Python 2.7.13. If you see that your OS is using Python 2 by default, you’ll need to change it to use the Python 3 installation. We want to this so that Python 3 is used every time we log in.
Enter the command:
nano ~/.bashrc
.bashrc is a file that resides in the user’s home directory (the user pi in this case). The file acts as a shell script that is run each time that specific user opens a terminal (or logs in over SSH, Serial, etc.). It can help to customize the user environment, and you will likely see a number of other commands already in there.
Scroll down to the bottom, and add the following command to the file:
alias python='/usr/bin/python3'
Exit out of nano by pressing ctrl+x, press the y key when prompted if you want to save the file, and press the enter key.
Instead of logging out and logging back in again to run the new command, you can simply run the contents of the .bashrc script by entering:
source ~/.bashrc
Now, check the version of Python again:
python --version
You should see some version of Python 3 being used.
Install pip
Full Desktop: If you are using the full desktop version of Raspbian, you should have pip already installed.
Headless: If you are using Raspbian Lite, the Python package manager, pip, does not come pre-installed. As a reult, you will need to install it with the commands:
Note that to use pip for Python 3, you will need to use the command pip3. However, we can modify the .bashrc file to use pip instead of pip3, as the rest of the tutorial will show examples using pip:
nano ~/.bashrc
Scroll down to the bottom, and add the following command to the file:
alias pip=pip3
Exit out of nano with ctrl+x, press y and enter. Run the .bashrc script with:
source ~/.bashrc
You should now be able to install Python packages using the pip command.
Hello, World!
One of the coolest features of Python is that it is an interpreted language (OK, in reality, Python scripts are first compiled to some bytecode, and that bytecode is interpreted). This means that we don’t need to run a separate compile step (i.e. translate our program into machine code) in order to run our program. In fact, we can even run the interpreter in what’s known as interactive mode. This will allow us to test out commands one line at a time!
To start, we’ll tell Python to print the phrase, “Hello, World!” to the terminal. We’ll do this first from the interpreter and then we’ll create a file and run it as a program. This will show you two of the main ways to interact with Python.
If you are curious about where the phrase “Hello, World!” comes from, this Wikipedia article gives a brief yet fascinating history.
Getting Started with the Interpreter
From a terminal, enter the following commend to start the Python interpreter:
python
You should be presented with a different command prompt, consisting of 3 greater-than signs >>>.
Type the following command:
print("Hello, World!")
Once you press enter, you should see the phrase Hello, World! repeated back to you.
And that’s it! You just ran your first Python program!
Exit out of the interpreter by entering:
exit()
Running a Python Program from a File
You can individually enter and run commands one line at a time in the Python interpreter, which is incredibly useful for trying out different commands (or using it as a calculator!). Often, you will want to save your commands together in one or more files so that you can run them all at once.
The simplest way to do this is to create a file from the terminal, although you are welcome to use the Raspbian graphical editor, Leafpad, as well (found under Accessories > Text Editor when you click on the Raspberry Pi icon in the top left).
Still in a terminal, enter the command:
nano hello.py
This creates a file named hello.py in your home directory (/home/pi) and starts editing it with the nano program.
In this file, enter the following on the first line:
print("Hello, World!")
Save and exit (ctrl+x followed by y and then enter). Back in the Linux command prompt, enter the command:
python hello.py
This should run the code found in the file hello.py. In our case, you should see the iconic phrase Hello, World! printed out in the console.
Note: In case you were wondering, I am clearing my terminal between screenshots with the clear command.
To summarize what we just did, you can use the python command on its own to begin an interactive interpreter session that allows you to type commands in real time. If you specify a file, such as python <FILE>.py, the Python interpreter will run the commands found in the file without giving you an interactive session.
Note that the filename suffix .py is not required for the interpreter to run the code found inside. However, it can be very helpful to keep your files organized so that when you see a file ending in .py, you will know that it contains Python code. The .py suffix is also necessary when making modules, which we’ll cover later.
Development Environments
The simplest way to create Python programs is to write your code in a text editor (e.g. nano, vim, emacs, Midnight Commander, Leafpad, etc.), save it, and then run it from the terminal with the command python <FILE>.py. You are welcome to continue working through this guide using a text editor and command line.
Some users prefer to use an integrated development environment (IDE) when developing code. IDEs offer a number of benefits including syntax highlighting, code completion, one-click running, debugging hints, etc. However, most IDEs require a graphical interface to use, which means you will need to be on the full desktop version of Raspbian.
Note: Out of the box, Raspbian comes with three Python IDEs: IDLE, Geany, and Thonny. I show a brief introduction to each below, and you are welcome to use them or any other text editor or IDE you so choose.
IDLE
IDLE is the default Python editor that has been available on Raspbian for many generations. The good news is that it has a built-in interpreter, which allows you to run commands one at a time to test code. The bad news is that it doesn’t show line numbers, and it only works with Python (but you’re only here for Python anyway, right?).
Open IDLE by selecting the Raspberry Pi logo in the top-left, and click Programming > Python 3 (IDLE). You should be presented with the Python interactive interpreter.
To write a program, go to File > New File. Enter in your code.
Click File > Save As… to save your code to a Python file (don’t forget the .py suffix!). Click Run > Run Module to run your program.
Geany
Geany is a great, beginner-friendly IDE that works with many different languages. However, it does not start up with a Python interactive interpreter. You can open Geany up by click on the Raspberry Pi logo in the top-left, and selecting Programming > Geany. Write your code in the file editor pane.
Save your code, making sure the filename ends with .py.
By default, Geany will attempt to open a new window to show the output of your code, which may or may not work on the Raspberry Pi. We can change it to run in the Terminal pane. Click Edit > Preferences. Select the Terminal tab and click to enable Execute programs in the VTE. Press enter to save and close the Preferences window.
Click Build > Execute (or click the paper airplane icon) to run your code. You should see the output of your program appear in the Terminal pane of Geany.
Thonny
Finally, Thonny is another great, easy-to-use IDE that comes pre-loaded on Raspbian. It focuses on Python and has an interactive environment when you load the program. Start Thonny by clicking on the Raspberry Pi icon followed by Programming > Thonny Python IDE.
Write your program in the top pane, click File > Save as… to save it, and click Run > Run current script to execute the program. Output will appear in the bottom interpreter pane.
Opinion: If you are just starting your journey in programming, we recommend Thonny for a graphical IDE and using nano if you are using a headless Raspberry Pi setup.
Programming in Python
The bulk of this tutorial focuses on controlling hardware connected to the Raspberry Pi. To accomplish that, we will be using the Python programming language. As such, you should be familiar with some of the basics of Python, including literals, variables, operators, control flow, scope, and data structures. Depending on your comfort level with Python, we recommend the following:
Not familiar: Read the recommended documentation and attempt the challenges
Somewhat familiar: Attempt the challenges and refer to the documentation when you run into trouble
Very familiar: Feel free to skip this whole section!
Since we don’t want to reinvent the wheel (there are many great tutorials and books out there on Python!), we will be referencing two texts:
A Byte of Python - A free, well-written introduction to the Python language. Concepts are covered briefly and succinctly with examples. Paper copies can be found here for purchase (besides, it helps support the author!).
The Python Documentation - A more technical and in-depth look at the Python language, which consists of a set of tutorials and reference guides. Refer to this if you need additional help understanding a concept.
Note: Some of the links for "A Byte of Python" might not open to the correct location on the page for some browsers. If this happens, just refresh the page to take you there.
Try it! Each of the code examples given can be run as separate programs. Try typing them out into the interpreter or copying them into a file (one example at a time) and running them with Python!
If you would like additional help with programming in Python (more examples and exercises) than what’s provided here, check out the following free sites: Non-Programmer’s Tutorial for Python 3, learnpython.org, and Google’s Python Class. After covering the basics of the Python language in this section, we’re going to dive into flashing lights, reading sensors, and controlling motors!
Comments
A comment is any text to the right of the pound (or hash) symbol #. The Python interpreter ignores this text, and it can be useful for writing notes to yourself or other programmers about what’s going on in the code.
Example:
# This is a comment and is not seen by the interpreter
print("Hello, World!")
Literals, also known as literal constants, are fixed values and include integers (e.g. 42), floating-point numbers (e.g. 6.23), and strings (e.g. “Hello, World!”). Note that strings need to be in between single quotation marks (‘ ’) or in between double quotation marks (“ ”).
Challenge: Store your name in a variable and then print that variable’s value to the terminal.
name = "your name"
print(name)
Logical Lines
So far, we’ve been writing one expression per line in our program. For example:
message = "hello!"
print(message)
You can combine these two lines into one line by separating them with a semicolon ;:
message = "hello"; print(message)
These two programs will execute in exactly the same fashion. That being said, it’s often recommended that you write programs with one logical line per physical line to make your code easier to read.
You can ask a user to enter information into the terminal by using the input() function. This will prompt the user to type out some text (including numbers) and then press enter to submit the text. Upon submission, the input() function will read the text and then return it as a string, which can be stored in a variable.
Whatever is in between the parentheses (known as arguments) will be printed to the screen prior to accepting user input.
Functions are sections of code that can be called by name. For example, print() is a function that takes the arguments and prints it out to the terminal. Notice in the example below that we separated two different arguments in print() by a comma. In this case, print() will print the different strings (or variables) in order on one line.
Note that you can use the int() function to turn a string into an integer (assuming the string is an integer).
Examples:
message = input("Type a message to yourself: ")
print("You said:", message)
number = int(input("Type a number:"))
print("You entered:", number)
Challenge: Write a program that asks for the user’s first name and last name (two separate input() calls) and then prints the user’s first and last name on one line. An example of this program running should look like:
first_name = input("Enter your first name: ")
last_name = input("Enter your last name: ")
print("Full name:", first_name, last_name)
Indentation
White space (number of spaces) at the beginning of a line is important in Python. Statements that form a group together must have the same level of indentation (amount of white space in front of the line). This will be important when we get into control flow statements (e.g. if, for) and functions.
If you have written programs in other languages before, you might be familiar with curly braces {}. In other languages, code in between these curly braces would form a group (or block) of code. In Python, a group (or block) of code is designated by the level of indentation of the individual lines of code.
Example:
answer = "yes"
guess = input("Is the sky blue? ")
if guess == answer:
print("Correct!")
else:
print("Try again")
if statements will be covered later, but note how the print() functions are indented, and thus form separate code groups underneath if and else statements.
An operator is a symbol that tells the interpreter to perform some mathematical, relational, or logical operation on one or more pieces of data and return the result.
Mathematical operators perform basic math operations on numbers:
Operator
Description
Example
+
Adds two numbers
2 + 3 returns 5
-
Subtracts one number from another
8 - 5 returns 3
*
Multiplies two numbers together
4 * 6 returns 24
**
Raises the first number to the power of the second number
2 ** 4 returns 16
/
Divides the first number by the second number
5 / 4 returns 1.25
//
Divides the two numbers and rounds down to the nearest integer (divide and floor)
5 / 4 returns 1
%
Divides the first number by the second number and gives the remainder (modulo)
19 % 8 returns 3
Logical operators compare two numbers and returns one of the Boolean values: True or False.
Operator
Description
Example
<
True if the first number is less than the second, False otherwise
5 < 3 returns False
>
True if the first number is greater than the second, False otherwise
5 > 3 returns True
<=
True if the first number is equal to or less than the second, False otherwise
2 <= 8 returns True
>=
True if the first number is equal to or greater than the second, False otherwise
2 >= returns False
==
True if the first number is equal to the second, False otherwise
6 == 6 returns True
!=
True if the first number is not equal to the second, False otherwise (not equal)
6 != 6 returns False
Compound logical operators require Boolean inputs and give a Boolean answer.
Operator
Description
Example
not
Gives the opposite (True becomes False and vice versa)
x = False; not x returns True
and
Returns True if both operands are True, False otherwise
x = True; y = False; x and y returns False
or
Returns True if either of the operands are True, False otherwise
x = True; y = False; c or y returns True
Bitwise operators perform binary operations on the bits (1s and 0s) of the given numbers. This tutorial talks more about binary and bitwise operations.
Operator
Description
Example
&
Returns a 1 in each bit position for which the corresponding bits of both operands are 1 (bitwise AND)
3 & 5 returns 1
|
Returns a 1 in each bit position for wich the corresponding bits of either or both operands are 1 (bitwise OR)
3 | 5 returns 8
^
Returns a 1 in each bit position for which the corresponding bits of either but not both operands are 1 (bitwise XOR)
3 ^ 5 returns 6
~
Inverts the bits in the given number (bitwise NOT)
~5 returns -6
<<
Shifts the bits of the first number to the left by the number of bits specified by the second number
5 << 2 returns 20
>>
Shifts the bits of the first number to the right by the number of bits specified by the second number
Challenge: Ask the user for two integers, and print the addition, subtraction, multiplication, division, and modulo of those numbers. For example, if you enter the numbers 6 and 7, the output should look like:
First number: 6
Second number: 7
13
-1
42
0.8571428571428571
6
The Python interpreter executes statements in your code from the top to the bottom of the file, in sequential order. That is unless, of course, we employ some time of control flow statements to break this normal sequential flow.
We introduce the range(x, y) function in the examples below, which generates a list of numbers between the first number, x (inclusive), and the second number, y (exclusive).
Statement
Description
Example
ifelifelse
If a condition is true, execute the block of code underneath the if statement. If not, see if the condition is true in one or more else if (elif) statements. If one of those is true, execute the code block under that. Otherwise, execute the code block underneath the else statement. elif and else statements are optional.
number = 42
guess = int(input("Guess a number between 1-100: "))
if guess == number:
print("You win!")
elif guess < number:
print("Nope")
print("Too low")
else:
print("Nope")
print("Too high")
print("Run the program to try again")
while
A while loop executes the block of code underneath it repeatedly as long as the condition is true.
Iterate over a sequence of numbers or objects. The variable declared in a for loop assumes the value of one of the numbers (or objects) during each iteration of the loop.
for i in range(1, 11):
print(i)
break
Use the break statement to exit out of a loop.
while True:
message = input("Tell me when to stop: ")
if message == "stop":
break
print("OK")
continue
The continue statement works similar to break, but instead of exiting the loop, it stops the current iteration and returns to the top of the loop.
for i in range(1, 6):
if i == 3:
continue
print(i)
Challenge: Write a program that prints integers counting up from 1 to 20, except that for every multiple of 3 (3, 6, 9, etc.), the word “fizz” is printed instead. The output should look like the following:
for i in range(1, 21):
if i % 3 == 0:
print("fizz")
else:
print(i)
Functions
Functions allow you to name a block of code and then reuse that code by calling its name. You can pass data to a function through variables known as parameters (note that the variables in the function definition are called parameters whereas the actual data itself being passed are known as arguments). Data can also be passed back to the calling statement using the return statement.
An example of a function definition would look like:
def functionName(parameter1, parameter2):
# Code goes here
You can call this function elsewhere in your code with functionName(argument1, argument2).
Note that variables declared inside the function definition are known as having local scope. This means that they cannot be accessed outside of that function. Variables declared at the top level of the program (i.e. outside any functions, loops, or classes) are known as having global scope and can be accessed anywhere in the program (including inside functions).
Important: Any functions you create must be defined before you use them! If you try to call a function higher up in the code (before its def definition), you’ll likely get an error such as:
NameError: name 'FUNCTION_NAME' is not defined
Python has a number of built-in functions that can help you (we’ve already seen three: print(), int(), and range()). A list of these functions can be found in the Python Tutorial.
Example:
def add(x, y):
sum = x + y
return sum
print(add(2, 3))
Challenge: Starting with the code below, implement the sumTo() function that takes an integer as a parameter (n), sums all the whole numbers from 1 to n (including n), and returns the sum. You may assume that the input, n, will always be a positive whole number (do not worry about handling negative numbers).
def sumTo(n):
# YOUR CODE GOES HERE
# Should be 1
print(sumTo(1))
# Should be 45
print(sumTo(9))
# Should be 5050
print(sumTo(100))
def sumTo(n):
sum = 0
for i in range(1, n + 1):
sum = sum + i
return sum
# Should be 1
print(sumTo(1))
# Should be 45
print(sumTo(9))
# Should be 5050
print(sumTo(100))
Objects
We have not talked about objects yet, but in reality, you’ve been using them all along. The secret to Python is that everything is an object. That’s right: everything, including functions and integers.
An object is simply a collection of data stored somewhere in your computer’s memory. What makes an object special in a programming language is the ability for it to store information and perform actions. We’ve already seen an object’s ability to store data (for example, a = 3 means that a is an integer object and stores the information 3). But how do we get an object to perform an action?
Objects are given a set of functions as defined by their class, which acts as a blueprint–telling the objects what they can and can’t do or information it can and can’t hold. When we talk about functions within a class (or objects), we call them methods.
For example, we can test if a floating point number is an integer by using the built-in is_integer() method. Note that this method is only accessible from float objects! We can’t call is_integer() on its own, so we use the dot-notation (.) to have the float object call the is_integer() method from within itself.
Example:
a = 3.0
b = 7.32
print(a.is_integer())
print(b.is_integer())
Note that we can’t use an integer as a float! For example, if we said c = 8, then c is an integer, not a float! If c is an integer, there is no .is_integer() method in integers, so calling c.is_intger() would fail (and throw an interpreter error). Try it! To force an integer value to be a floating point number, we simply add .0 after it (just like we did with a = 3.0).
Challenge: Modify the code below so that the phrase stored in my_string is converted to all lower case letters and printed to the terminal. Hint: review the String Methods in the Python Reference Guide to find a built-in method to do this for you.
my_string = "THiS iS A TEst!"
# Should print "this is a test!"
# YOUR CODE GOES HERE
my_string = "THiS iS A TEst!"
# Should print "this is a test!"
print(my_string.lower())
Data Structures
In addition to variables and objects, Python has four other ways to store data: list, tuple, dictionary, set. These structures hold a collection of related data, and each of them has a slightly different way of interacting with it.
Structure
Description
Example
List
A list is a sequence of ordered items. You can access items in a list using brackets [] and an index (e.g. list[2]). Note that indeces are 0-based, which means list[0] will access the first item in the list. Because lists can be modified, they are known as mutable.
my_list = [1, 5, 73, -3]
my_list[2] = -42
# Get third item in list
print(my_list[2])
# Get all but first item in list
print(my_list[1:])
Tuple
A tuple works just like a list (an ordered set). The difference is that a tuple is immutable, which means you cannot change the values once they are set. A tuple is usually specified by parentheses (). While the parentheses are not necessary, they are highly recommended to make your code easier to read.
my_tuple = ("bird", "plane", 5, "train")
# Get one item from the tuple
print(my_tuple[0])
# Get a tuple of second and third items
print(my_tuple[1:3])
# Can't do this because tuples are immutable!
my_tuple[1] = "skyscraper"
Dictionary
A dictionary is a collection of assciated key and value pairs. Similar to how a phonebook works: you look up someone's name (key) and you find their phone number (value). Dictionaries are defined by curly braces {}, and key/value pairs are separated by a colon :. Dictionaries are mutable.
A set is a mutable, unordered collection with no duplicate elements. Sets are optimized for determining if an item is in the set (and you don't care about the order of items). Sets are great if you want to implement any sort of set theory in Python.
my_set_1 = set(["orange", "banana"])
my_set_2 = set(["apple"])
my_set_2.add("orange")
print(my_set_1)
print(my_set_2)
print("apple" in my_set_2)
print("strawberry" in my_set_2)
print(my_set_1.union(my_set_2))
print(my_set_1.intersection(my_set_2))
Challenge: Starting with the code below, implement the average() function to compute the average of the numbers given to it in list form. Hint: you will probably want to use the len() function.
def average(num_list):
# YOUR CODE GOES HERE
# Should print 5.0
list_1 = [4, 7, 9, 0]
print(average(list_1))
# Should print 4.406333333333
list_2 = [-3.2, 6.419, 10]
print(average(list_2))
# Should print 42.0
list_3 = [42]
print(average(list_3))
def average(num_list):
avg = 0
for n in num_list:
avg = avg + n
avg = avg / len(num_list)
return avg
# Should print 5.0
list_1 = [4, 7, 9, 0]
print(average(list_1))
# Should print 4.406333333333
list_2 = [-3.2, 6.419, 10]
print(average(list_2))
# Should print 42.0
list_3 = [42]
print(average(list_3))
Modules
Modules are another way to reuse code and help you organize your program. They are simply files that are imported into your main program. After importing a module, you can use a module in much the same way you would an object: access constants and functions using the dot-notation.
Example:
Save the following file as stringmod.py:
a = 42
def string_to_list(s):
c_list = []
for c in s:
c_list.append(c)
return c_list
In the same folder as stringmod.py, run the following code (either in the interpreter or saved as a file):
import stringmod
s = "Hello!"
print(stringmod.a)
print(stringmod.string_to_list(s))
Challenge: Python comes with several standard modules that you can import into your program. One of them is the math module, which you can use with import math. Use the constants and functions found in the math module to perform the following actions:
Print the ceiling of 3.456 (should be 4)
Print the square root of 9216 (should be 96.0)
Calculate and print the area of a circle whose radius is 2 (should be 12.566370614359172)
import math
print(math.ceil(3.456))
print(math.sqrt(9216))
print(math.pi * (2 ** 2))
Finding and Fixing Bugs
Programs almost never work right away, so don’t sweat it! The art and science of finding and fixing problems in your code is known as debugging. The most helpful debugging tool is the standard Python output. If there is a problem with your code, it will likely tell you where the error is and what’s wrong with it.
For example, let’s take the following code snippet. In it, we forget to indent our code underneath our for loop.
a = [1, 2, 3, 4]
for n in a:
print(n)
If you run this code, you’ll see that the Python interpreter is super helpful by saying it was looking for an indented piece of code around line 4.
File "test_01.py", line 4
print(n)
^
IndentationError: expected an indented block
Here’s another example. Can you spot the error?
a = [1, 2, 3, 4]
for n in a
print(n)
We forgot the colon after the for loop! The interpreter will let us know by telling us:
File "test_01.py", line 3
for n in a
^
SyntaxError: invalid syntax
“Invalid syntax” is a little vague, but it tells you to look around line 3 for something that might be wrong, such as a missing colon, too many parentheses, a single quote instead of a double quote, etc.
What happens if your code runs, but it doesn’t output the value(s) you expect? This is on you, the programmer, to find and fix! Adding print() statements throughout your code can help you identify where something might have went wrong.
Try running this code:
for i in range(1, 10):
if i == 10:
print("end")
Why don’t you see “end” appear in the terminal? To help diagnose this problem, we can add a print() statement to see what’s going on:
for i in range(1, 10):
print(i)
if i == 10:
print("end")
When we run this, we see the values of i printed in order.
A-ha! It turns out that i never reaches 10. That’s because the second number in the range() function is exclusive. If we need it to count to 10, then we should change it to:
Challenge: Find the 7 errors in the program below. When you fix the errors and run it, it should print numbers from 1 to the first argument, replacing multiples of the second argument with the word “buzz”.
print("Count to 7, buzz on 2's")
buzz(7, 2)
print('Count to 10, buzz on 5's")
buzz(10, 5)
def buzz(n):
for i in range(1, n):
if i % z = 0:
print("buzz")
else
print(n)
Here is the program running successfully:
def buzz(n, z): # Added z as second parameter
for i in range(1, n + 1): # n should be n + 1
if i % z == 0: # Change single equal sign to double equal sign
print("buzz")
else: # Add colon
print(i) # Change 'n' to 'i'
print("Count to 7, buzz on 2's")
buzz(7, 2) # Move calls to buzz() underneath function definition
print("Count to 10, buzz on 5's") # Change single quote to double quote
buzz(10, 5)
Experiment 1: Digital Input and Output
In the embedded world, the first thing many developers like to do is blink an LED. In a sense, it is the “Hello, World!” of embedded electronics. It proves that we can run code and control some hardware (with immediate, and often amusing, results). In this section, we’ll start by blinking an LED, and we’ll take it a step further by also responding to a push button.
Recommended Reading
Python (RPi.GPIO) API - Overview of the RPi.GPIO module, which we’ll be using throughout this tutorial to control hardware
What is a Circuit? - Talks about how electricity moves through a circuit
Polarity - Shows why we need to put the LED in the circuit a certain way
How to Use a Breadboard - Breadboards are great for prototyping, and we use them in this tutorial
Raspberry Pi Pinout
One of the things that makes the Raspberry Pi better for learning electronics than most other computers is its ability to control the voltage on several of its easily accessible pins. If you hold your Pi facing up in portrait mode (as shown in the photo below), on the right side, you will see a header with 40 pins. This header contains outputs for 3.3V, 5V, Ground, and lots of General Purpose Input/Output (GPIO) pins!
Note that pin 1 is on the top left of the header, as shown in the photo. With pin 1 in this position, we can see what each of the pins is used for:
Hardware Connections
You can connect the Raspberry Pi to the LED and button directly, or you can go through the SparkFun Pi Wedge to make the connections easier on a breadboard. The important thing is to note that we are using the GPIO numbers in our code (listed as Gx on the Pi Wedge, where x is the GPIO number). These GPIO numbers are shown in the yellow boxes in the GPIO Pinout diagram above.
Connect GPIO12 (pin 32) to the 330Ω resistor, and the resistor to the LED
Connect GPIO4 (pin 7) to the button
Make the power (3.3 V) and ground (GND) connections as shown in the Fritzing diagram
Having trouble seeing the diagrams? Click on them to see the full-size version!
If you have a Pi Wedge, it can make connecting to external hardware on a breadboard easier. If you don’t, you can still connect directly to the Raspberry Pi with jumper wires.
Connecting through a Pi Wedge:
Connecting directly to the Raspberry Pi:
Note: It matters how you plug in your LED! Current can only flow in one direction through an LED, so pay careful attention to the leads. The long lead on the LED should be connected on the same row as the 330Ω resistor.
Note: Buttons can be a little weird, if it's the first time you've used them. The pins across from each other are always connected, whereas the pins on the same side are only connected when you push the button.
Note: If you are using the full-size breadboard, the power rails are divided in the middle. This means that to get power to the whole power row, you will need to connect the two halves. See the picture below to see how to use jumper wires to connect the halves of the power rows.
Code Part 1: Blinking an LED
Depending on your version of Raspbian, you may or may not have to install the RPi.GPIO package (e.g. Raspbian Lite does not come with some Python packages pre-installed). In a terminal, enter the following:
pip install rpi.gpio
In a new file, enter the following code:
import time
import RPi.GPIO as GPIO
# Pin definitions
led_pin = 12
# Suppress warnings
GPIO.setwarnings(False)
# Use "GPIO" pin numbering
GPIO.setmode(GPIO.BCM)
# Set LED pin as output
GPIO.setup(led_pin, GPIO.OUT)
# Blink forever
while True:
GPIO.output(led_pin, GPIO.HIGH) # Turn LED on
time.sleep(1) # Delay for 1 second
GPIO.output(led_pin, GPIO.LOW) # Turn LED off
time.sleep(1) # Delay for 1 second
Save the file (I named my file blink.py). Run the code from the terminal by entering:
python blink.py
You should see your LED begin to blink on and off every second:
Once you’ve gotten bored of watching the LED, end the program by pressing ctrl + c.
Troubleshooting: If you see the message “ModuleNotFoundError: No module named ‘rpi’” you will need to install the RPi.GPIO package by entering pip install RPi.GPIO in a terminal.
Code to Note:
To control hardware from the Raspberry Pi, we rely on the RPi.GPIO module. This module (likely known as a “library” in other languages) is specifically designed to help us toggle pins and talk to other pieces of hardware. Lucky for us, it comes pre-packaged with Raspbian!
In the first two lines, you see that we imported modules, but we added a few things onto those imports. First up, we used the keyword as:
import RPi.GPIO as GPIO
RPi.GPIO is the name of the module. By saying as GPIO, we change how we want to refer to that module in the rest of the program. This allows us to type
GPIO.output(led_pin, GPIO.HIGH)
instead of the much longer
RPi.GPIO.output(led_pin, RPi.GPIO.HIGH)
While it’s generally not a good idea to disable warnings while coding, we added the following line:
GPIO.setwarnings(False)
Without it, you’ll get a warning from the interpreter when you try to run the blink program again:
blink.py:14: RuntimeWarning: This channel is already in use, continuing anyway. Use GPIO.setwarnings(False) to disable warnings.
GPIO.setup(led_pin, GPIO.OUT)
This is because we did not shut down the GPIO 12 pin nicely when we exited the program. To do this, we would want to add a GPIO.cleanup() line at the end of our program. However, because we wrote our program to run forever, we have to interrupt the program to stop it (and a call to cleanup() would never occur). For the time being, it’s enough to just ignore the warnings.
Challenge: Change the program to make the LED blink like a heartbeat: 2 quick flashes in succession and then a longer delay.
import time
import RPi.GPIO as GPIO
# Pin definitions
led_pin = 12
# Suppress warnings
GPIO.setwarnings(False)
# Use "GPIO" pin numbering
GPIO.setmode(GPIO.BCM)
# Set LED pin as output
GPIO.setup(led_pin, GPIO.OUT)
# Blink forever
while True:
GPIO.output(led_pin, GPIO.HIGH)
time.sleep(0.2)
GPIO.output(led_pin, GPIO.LOW)
time.sleep(0.2)
GPIO.output(led_pin, GPIO.HIGH)
time.sleep(0.2)
GPIO.output(led_pin, GPIO.LOW)
time.sleep(1)
Code Part 2: Fading an LED with PWM
We’ve seen how to turn an LED on and off, but how do we control its brightness levels? An LED’s brightness is determined by controlling the amount of current flowing through it, but that requires a lot more hardware components. A simple trick we can do is to flash the LED faster than the eye can see!
By controlling the amount of time the LED is on versus off, we can change its perceived brightness. This is known as pulse width modulation (PWM). We have two separate PWM channels for our use: PWM0 and PWM1. We can output a PWM signal on PWM0, which will show up on GPIO12 and GPIO18. Additionally, PWM1 controls the signal for GPIO13 and GPIO19.
Copy the following code into a file (e.g. pwm.py):
import time
import RPi.GPIO as GPIO
# Pin definitions
led_pin = 12
# Use "GPIO" pin numbering
GPIO.setmode(GPIO.BCM)
# Set LED pin as output
GPIO.setup(led_pin, GPIO.OUT)
# Initialize pwm object with 50 Hz and 0% duty cycle
pwm = GPIO.PWM(led_pin, 50)
pwm.start(0)
# Set PWM duty cycle to 50%, wait, then to 90%
pwm.ChangeDutyCycle(50)
time.sleep(2)
pwm.ChangeDutyCycle(90)
time.sleep(2)
# Stop, cleanup, and exit
pwm.stop()
GPIO.cleanup()
Run it (e.g. python pwm.py), and you should see the LED start dim, wait 2 seconds, grow brighter, wait another 2 seconds, and then turn off before exiting the program.
Code to Note:
In the first part, we use the .output() function of the GPIO module to toggle the LED. Here, we create PWM object and store it in the variable pwm. We do this with the line:
pwm = GPIO.PWM(led_pin, 50)
From there, we can control the PWM by calling methods within that object. For example, we change the brightness by calling:
pwm.ChangeDutyCycle(t)
where t is some number between 0-100 (0 being off and 100 being always on). Putting in the number 50 would mean that the LED is on half the time and off the other half the time (it’s just toggling so fast that you can’t see it!).
Also, we left out the GPIO.setwarnings() call, since we can actually call GPIO.cleanup() at the end of our program! If you try to run the PWM code twice, you should not see any warnings.
Challenge: Make the LED slowly fade from off to fully bright over the course of about 2 seconds. Once it has reached maximum brightness, the LED should turn off and repeat the fading process again. Have the LED fade on over and over again forever.
import time
import RPi.GPIO as GPIO
# Pin definitions
led_pin = 12
# Use "GPIO" pin numbering
GPIO.setmode(GPIO.BCM)
# Set LED pin as output
GPIO.setup(led_pin, GPIO.OUT)
# Initialize pwm object with 50 Hz and 0% duty cycle
pwm = GPIO.PWM(led_pin, 50)
pwm.start(0)
# Have the LED slowly fade up, turn off, and repeat
while True:
for brightness in range(0, 101):
pwm.ChangeDutyCycle(brightness)
time.sleep(0.02)
Code Part 3: Button Input
Let’s add some user input! Save the following to a file (such as button.py).
import time
import RPi.GPIO as GPIO
# Pins definitions
btn_pin = 4
led_pin = 12
# Set up pins
GPIO.setmode(GPIO.BCM)
GPIO.setup(btn_pin, GPIO.IN)
GPIO.setup(led_pin, GPIO.OUT)
# If button is pushed, light up LED
try:
while True:
if GPIO.input(btn_pin):
GPIO.output(led_pin, GPIO.LOW)
else:
GPIO.output(led_pin, GPIO.HIGH)
# When you press ctrl+c, this will be called
finally:
GPIO.cleanup()
Run it the code (python button.py). Now, when you press the button, the LED should turn on.
Code to Note:
The first odd thing you might notice is the try: and finally: statements. These are part of the error and exception handling abilities in Python (you can read more about them in the Exceptions chapter in Byte of Python).
If we press ctrl + x while the program is inside the while True: loop, an exception will be thrown. We don’t care to do anything with that exception (hence why you don’t see an except block, like you might have read about in “exception handling”). Regardless of whatever the exception is, we do want to call our GPIO.cleaup() function. That way, we can close down the GPIO and not have to worry about any more errors!
The other odd thing you might see is that if GPIO.input(btn_pin) is True (which means the pin is logic high, or 3.3V), we turn the LED off. Wait, what?
In our circuit, our button has a pull-up resistor connecting one of the pins to a constant 3.3V. This means that in its default state (not pressed), the pin connected to the button is 3.3V. When we press the button, the pin is connected to ground (through the button’s internal contacts), and the pin becomes logic low (0V).
As a result, when the button is not pressed, we get logic high (GPIO.input() returns True), and when the button is pressed, we get logic low (GPIO.input() returns False).
Challenge: Write a program so that whenever you press the button, a variable is incremented by one and is printed to the screen. This should work as a simple button counter. Start at 0, and each time you press the button, it counts up on the screen.
import time
import RPi.GPIO as GPIO
# Pins definitions
btn_pin = 4
# Set up pins
GPIO.setmode(GPIO.BCM)
GPIO.setup(btn_pin, GPIO.IN)
# Our counter
counter = 0
# Remember the current and previous button states
current_state = True
prev_state = True
# If button is pushed, light up LED
try:
while True:
current_state = GPIO.input(btn_pin)
if (current_state == False) and (prev_state == True):
counter = counter + 1
print(counter)
prev_state = current_state
# When you press ctrl+c, this will be called
finally:
GPIO.cleanup()
Experiment 2: Play Sounds
Downloading audio clips and playing them on a Raspberry Pi is quite simple. We will use the command line to download a .wav file, adjust the audio, and test playing the file. Then, we’ll write a Python script to play that file whenever we press a button!
Recommended Reading
amixer - We will be using the amixer Linux tool to adjust the volume on our Raspberry Pi
Pygame - Pygame is a framework that is used for making simple games in Python. Raspbian comes pre-loaded with Pygame, which means we can use it to play sounds.
Hardware Connections
Good news, everyone! We will be using the same circuit from the previous experiment.
Connect GPIO12 (pin 32) to the 330Ω resistor, and the resistor to the LED
Connect GPIO4 (pin 7) to the button
Make the power (3.3 V) and ground (GND) connections as shown in the Fritzing diagram
Connecting through a Pi Wedge:
Connecting directly to the Raspberry Pi:
You will also need to plug an external speaker (or a set of headphones) into the Pi’s headphone jack. If you are using the Hamburger Mini Speaker, make sure it is charged and turned on.
Configure Audio
Before we write code, we need to configure the audio from the command line. Open a terminal (if you are using Raspbian with a desktop).
Note: Make sure you have selected the 3.5mm ('headphone') jack as your output audio device from the sudo rasp-config advanced options. Refer to the Configure Your Pi section to see how to do this.
From a terminal, enter the following commands:
amixer set PCM unmute
amixer set PCM 100%
Verify that your audio is on and up by entering the command:
amixer
At the end of the printout, you should see Mono: Playback 400 [100%] [4.00dB] [on].
Download a free sound clip (we’ll go with some applause, because we’re awesome):
You should hear some nice cheering and clapping out of your speaker (or headphones).
Code: Push Button, Get Sound
Depending on your version of Raspbian, you may or may not have to install the pygame package (e.g. Raspbian Lite does not come with some Python packages pre-installed). In a terminal, enter the following:
import time
import RPi.GPIO as GPIO
from pygame import mixer
# Pins definitions
btn_pin = 4
# Set up pins
GPIO.setmode(GPIO.BCM)
GPIO.setup(btn_pin, GPIO.IN)
# Initialize pygame mixer
mixer.init()
# Remember the current and previous button states
current_state = True
prev_state = True
# Load the sounds
sound = mixer.Sound('applause-1.wav')
# If button is pushed, light up LED
try:
while True:
current_state = GPIO.input(btn_pin)
if (current_state == False) and (prev_state == True):
sound.play()
prev_state = current_state
# When you press ctrl+c, this will be called
finally:
GPIO.cleanup()
Save the file (e.g. applause.py), and start the program with python applause.py. Push the button, and you should hear some congratulatory sounds!
Troubleshooting: If you see the message “ModuleNotFoundError: No module named ‘pygame’” you will need to install the pygame package by entering pip install pygame in a terminal.
Code to Note:
To play sounds, we are using the pygame package. A package in Python is a collection of modules grouped together. Lucky for us, pygame comes pre-installed with Python on Raspbian. To use it, we just need to use from pygame in our code, and we can specify which module we want to use by saying import after it. For example, we might say:
from pygame import mixer
This says that we want to import the mixer module from the pygame package. Later in our code, we can use the mixer module to create a Sound object with:
sound = mixer.Sound('applause-1.wav`)
Our downloaded file, applause-1.wav is used to create a Sound object, which we store in the sound variable. We can call the .play() method in our Sound object to start playing the .wav file.
sound.play()
Challenge: You might have noticed that if you press the button again while the sound is playing, an new sound will start that overlaps the first clip. Let’s fix this! Change the code so that when you press the button while the sound is playing, the sound stops. When you press it again, the sound clip starts over again. Also, because we can, have the LED light up while the sound is playing. Hint: it might help to look at the pygame.mixer methods for determining if sound is being played (or “mixed”) and how to stop a sound.
import time
import RPi.GPIO as GPIO
from pygame import mixer
# Pins definitions
btn_pin = 4
led_pin = 12
# Set up pins
GPIO.setmode(GPIO.BCM)
GPIO.setup(btn_pin, GPIO.IN)
GPIO.setup(led_pin, GPIO.OUT)
# Initialize pygame mixer
mixer.init()
# Remember the current and previous button states
current_state = True
prev_state = True
# Load the sounds
sound = mixer.Sound('applause-1.wav')
# If button is pushed, light up LED
try:
while True:
# If button is pressed, turn on LED and play sound
current_state = GPIO.input(btn_pin)
if (current_state == False) and (prev_state == True):
if mixer.get_busy():
sound.stop()
else:
GPIO.output(led_pin, GPIO.HIGH)
sound.play()
# Only turn off LED if sound has stopped playing
if mixer.get_busy() == False:
GPIO.output(led_pin, GPIO.LOW)
# Save state of switch to use in next iteration of the loop
prev_state = current_state
# When you press ctrl+c, this will be called
finally:
GPIO.cleanup()
Experiment 3: SPI and Analog Input
Many sensors out there use an analog voltage to convey their measurement data. For example, photocells change their resistance depending on how much light is falling on the sensor. By using a voltage divider in our circuit, we can effectively measure the amount of ambient light by measuring a voltage.
The bad news is that our Raspberry Pi does not come with any way to measure an analog voltage. To do that, we’ll need to rely on a separate piece of circuitry: an analog-to-digital converter (ADC). Specifically, we’ll be using the Microchip MCP3002, which is a niftly little chip that can measure up to 2 analog voltages on separate channels and report their values over the Serial Peripheral Interface (SPI) interface.
We’ll use the built-in spidev module in Python to send commands and read replies on the SPI bus.
Recommended Reading
Analog vs. Digital - What is the difference, and why do we need to care about them for this example?
Voltage Dividers - Explains how to set up, use, and calculate the values in a voltage divider
Binary - We’ll be working directly in binary in this section
Hardware Connections
Refer to the Raspberry Pi Pinout section in the previous example if you would like to see what pins and GPIO labels belong to each of these connections.
Connect MOSI (GPIO10, pin 19) to Din on the MCP3002
Connect MISO (GPIO9, pin 21) to Dout on the MCP3002
Connect SCLK (GPIO11, pin 21) to CLK on the MCP3002
Connect CE0 (GPIO8, pin 24) to CS/SHDN on the MCP3002
Connect the photocell voltage divider to CH0 on the MCP3002
Connect the potentiometer’s middle pin to CH1 on the MCP3002
Make the power (3.3 V) and ground (GND) connections as shown in the Fritzing diagram
Connecting through a Pi Wedge:
Connecting directly to the Raspberry Pi:
Note: Pay close attention to how the MCP3002 is oriented. You should see a notch in the top surface of the chip. With the notch oriented up, pin 1 is down and to the left of the notch.
Depending on your version of Raspbian, you may or may not have to install the spidev package (e.g. Raspbian Lite does not come with some Python packages pre-installed). In a terminal, enter the following:
pip install spidev
In a new file, enter the following code:
import time
import spidev
spi_ch = 0
# Enable SPI
spi = spidev.SpiDev(0, spi_ch)
spi.max_speed_hz = 1200000
def read_adc(adc_ch, vref = 3.3):
# Make sure ADC channel is 0 or 1
if adc_ch != 0:
adc_ch = 1
# Construct SPI message
# First bit (Start): Logic high (1)
# Second bit (SGL/DIFF): 1 to select single mode
# Third bit (ODD/SIGN): Select channel (0 or 1)
# Fourth bit (MSFB): 0 for LSB first
# Next 12 bits: 0 (don't care)
msg = 0b11
msg = ((msg << 1) + adc_ch) << 5
msg = [msg, 0b00000000]
reply = spi.xfer2(msg)
# Construct single integer out of the reply (2 bytes)
adc = 0
for n in reply:
adc = (adc << 8) + n
# Last bit (0) is not part of ADC value, shift to remove it
adc = adc >> 1
# Calculate voltage form ADC value
voltage = (vref * adc) / 1024
return voltage
# Report the channel 0 and channel 1 voltages to the terminal
try:
while True:
adc_0 = read_adc(0)
adc_1 = read_adc(1)
print("Ch 0:", round(adc_0, 2), "V Ch 1:", round(adc_1, 2), "V")
time.sleep(0.2)
finally:
GPIO.cleanup()
Save the file (e.g. adc.py), and run it with Python:
python adc.py
You should be able to cover the photocell and see the Ch 0 voltage change. Adjust the knob on the potentiometer to see the Ch 1 voltage change.
Code to Note:
We are using SPI channel 0 on the Raspberry Pi when we initialize the SpiDev object:
spi_ch = 0
spi = spidev.SpiDev(0, spi_ch)
Channel 0 corresponds to using CE0 (chip enable 0) on the Pi’s pins. If you wanted to use another device on the SPI bus, you would need to connect it to CE1 and use SpiDev channel 1 as well.
We construct our SPI message by manipulating individual bits. We start with binary 11 (which is the decimal number 3) by using the prefix ‘0b’:
msg = 0b11
If you look at section 5 in the MCP3002 datasheet, you will see that we need to send a 1 to start transmission, followed by another 1 to denote that we want “single ended mode.” After that, we select our channel with a 0 (for channel 0) or a 1 (for channel 1). Then, we send a 0 to show that we want data returned to us with the least significant bit (LSB) sent first.
msg = ((msg << 1) + adc_ch) << 5
Finally, we send another twelve 0s. What we send here really doesn’t matter, as we just need to send clock pulses to the MCP3002 so that it sends data back to us over the Dout (MISO) line. This data (4 setup bits followed by twelve 0s) is stored in a list.
msg = [msg, 0b00000000]
We store the data returned to us in the reply variable, and it comes to us as a list of 2 bytes (stored as 2 integers). Note that we send out data and read the reply at the same time when using SPI:
reply = spi.xfer2(msg)
From there, we construct a single integer out of the two bytes (8 bits) by shifting the first over to the left by 8 bits and then adding the second byte to it. The last bit we read in is extraneous (not part of the ADC’s return value) so we shift the answer to the right by one bit.
adc = 0
for n in reply:
adc = (adc << 8) + n
adc = adc >> 1
The ADC value is given as a percentage of the maximum voltage (whatever the voltage is on the Vdd/Vref pin). That percentage is calculated by dividing the reply value by 1024. We get 1024 because we know that the MCP3002 is a 10-bit ADC, which means the maximum value of 10 bits (0b1111111111) is 1023. Many ADCs have some error, so we round up to 1024 to make the math easier (here’s a discussion on max ADC values, if you’re curious).
Once we get the percentage of Vref with val / 1024, we multiply that percentage by our Vref, which we know is 3.3V in the case of our Raspberry Pi.
voltage = (vref * adc) / 1024
And that’s how we get our analog voltage reading! If all this is confusing, you can simple copy the Enable SPI portion and read_adc() function into your own code. Then, just call read_adc(0) to get the voltage at CH0 on the MCP3002.
One last interesting bit of code is the idea of default parameters. If you take a look at the read_adc() definition:
def read_adc(adc_ch, vref = 3.3):
You’ll see that there are actually two parameters: adc_ch and vref. When you call this function, you are required to give it a channel number (0 or 1). However, you can optionally send it an argument with the Vref value. On most cases with the Raspberry Pi, the voltage will be 3.3V. If you use another voltage (e.g. 5V), then you can change the math so that the ADC gives you a more accurate reading.
You have the option of calling this function with another Vref (e.g. 5) using either read_adc(0, 5) or by explicitly naming the vref parameter read_adc(0, vref=5). However, because we know that we’ve connected 3.3V to the MCP3002, we can simply call read_adc(0) and know that the function will rely on its default parameter of vref=3.3 when doing its calculations.
Challenge: Add an LED to your circuitry. Write a program to act as a variable nightlight. That is, the LED should turn on whenever the photocell sees dark (little ambient light) and should turn off whenever the photocell sees light (lots of ambient light). Have the potentiometer control the brightness of the LED when it is on. Hint: you might want to take some measurements to determine the threshold of light vs. dark. What is the voltage when you cover the photocell with your hand?
Connect an LED and resistor to GPIO12 (pin 32), like in the previous experiment.
import time
import spidev
import RPi.GPIO as GPIO
# Pin definitions
led_pin = 12
# Light/dark threshold (Volts)
light_threshold = 2.2
# Use "GPIO" pin numbering
GPIO.setmode(GPIO.BCM)
# Set LED as output
GPIO.setup(led_pin, GPIO.OUT)
# Initialize pwm object with 50 Hz and 0% duty cycle
pwm = GPIO.PWM(led_pin, 50)
pwm.start(0)
# SPI channel (use CE0)
spi_ch = 0
# Enable SPI
spi = spidev.SpiDev(0, spi_ch)
spi.max_speed_hz = 1200000
def read_adc(adc_ch, vref = 3.3):
# Make sure ADC channel is 0 or 1
if adc_ch != 0:
adc_ch = 1
# Construct SPI message
# First bit (Start): Logic high (1)
# Second bit (SGL/DIFF): 1 to select single mode
# Third bit (ODD/SIGN): Select channel (0 or 1)
# Fourth bit (MSFB): 0 for LSB first
# Next 12 bits: 0 (don't care)
msg = 0b11
msg = ((msg << 1) + adc_ch) << 5
msg = [msg, 0b00000000]
reply = spi.xfer2(msg)
# Construct single integer out of the reply (2 bytes)
adc = 0
for n in reply:
adc = (adc << 8) + n
# Last bit (0) is not part of ADC value, shift to remove it
adc = adc >> 1
# Calculate voltage form ADC value
voltage = (vref * adc) / 1024
return voltage
# Read ADC values and determine if LED should be on or off
try:
while True:
# Read ADC values
light_val = read_adc(0)
knob_val = read_adc(1)
# Calculate brightness for when LED is on
# Max brightness is 100% duty cycle
brightness = (knob_val / 3.3) * 100
# Turn LED off if ambient light is above threshold
# Turn LED on if ambient light is equal to or below threshold
if light_val > light_threshold:
pwm.ChangeDutyCycle(0)
else:
pwm.ChangeDutyCycle(brightness)
time.sleep(0.1)
finally:
pwm.stop()
GPIO.cleanup()
Experiment 4: I2C Temperature Sensor
In addition to analog sensors and SPI chips, you’ll often find sensors (and other devices) that rely on the Inter-Integrated Chip (IIC or I2C) protocol. This is a 2-wire bus that contains a clock and data channel. The master (Raspberry Pi) and device (sensor) can communicate on the same data wire.
To see this protocol in action, we’ll write a program to talk to a TMP102 Temperature Sensor. We’ll use the smbus Python module to handle the low-level communication for us. Note that “SMBus” stands for “System Management Bus” and is another protocol layer built on top of the I2C protocol. By using smbus, we lose out on a few I2C abilities (e.g. clock stretching), but we can still talk to many I2C sensors.
Recommended Reading
I2C - A detailed look at how the I2C protocol works
Hardware Connections
Refer back to Experiment 1 to look at the Pinout chart.
Connect SDA1 (GPIO2, pin 3) to SDA on the TMP102
Connect SCL1 (GPIO3, pin 5) to SCL on the TMP102
Connect power (3.3 V) to VCC on the TMP102
Connect ground (GND) to GND on the TMP102
Connecting through a Pi Wedge:
Connecting directly to the Raspberry Pi:
Code: Read and Calculate Temperature
Depending on your version of Raspbian, you may or may not have to install the smbus package (e.g. Raspbian Lite does not come with some Python packages pre-installed). In a terminal, enter the following:
sudo apt-get install python3-smbus
In a new file, copy in the following code:
import time
import smbus
i2c_ch = 1
# TMP102 address on the I2C bus
i2c_address = 0x48
# Register addresses
reg_temp = 0x00
reg_config = 0x01
# Calculate the 2's complement of a number
def twos_comp(val, bits):
if (val & (1 << (bits - 1))) != 0:
val = val - (1 << bits)
return val
# Read temperature registers and calculate Celsius
def read_temp():
# Read temperature registers
val = bus.read_i2c_block_data(i2c_address, reg_temp, 2)
temp_c = (val[0] << 4) | (val[1] >> 5)
# Convert to 2s complement (temperatures can be negative)
temp_c = twos_comp(temp_c, 12)
# Convert registers value to temperature (C)
temp_c = temp_c * 0.0625
return temp_c
# Initialize I2C (SMBus)
bus = smbus.SMBus(i2c_ch)
# Read the CONFIG register (2 bytes)
val = bus.read_i2c_block_data(i2c_address, reg_config, 2)
print("Old CONFIG:", val)
# Set to 4 Hz sampling (CR1, CR0 = 0b10)
val[1] = val[1] & 0b00111111
val[1] = val[1] | (0b10 << 6)
# Write 4 Hz sampling back to CONFIG
bus.write_i2c_block_data(i2c_address, reg_config, val)
# Read CONFIG to verify that we changed it
val = bus.read_i2c_block_data(i2c_address, reg_config, 2)
print("New CONFIG:", val)
# Print out temperature every second
while True:
temperature = read_temp()
print(round(temperature, 2), "C")
time.sleep(1)
Save the file (e.g. tmp102.py), and run it with Python:
python tmp102.py
You should see the 2 bytes in the CONFIG register be updated and then the temperature is printed to the screen every second.
Code to Note:
Unlike SPI, I2C relies on a set of addresses and registers. This is because SPI is configured to talk to one chip at a time while I2C can share many devices on one bus. To avoid conflicts, addresses are assigned to devices (by the manufacturer) so that each one knows when the host (the Pi) is trying to talk to it. The address for our TMP102 is 0x48.
Each time we want to talk to the TMP102, we must send out its address (0x48) on the bus. Only then can we send the memory location (or address) of the register that we want to read from or write to on the TMP102. Note that for most I2C devices, a register is a location in the device’s memory that stores 8 bits (1 byte) of data. Sometimes, this data controls the function of the device (as in the case of the CONFIG register). Other times, the registers hold the sensor reading data (as in the case of the temperature register).
We use the following command to read 2 bytes from the temperature register in the TMP102:
val = bus.read_i2c_block_data(i2c_address, reg_temp, 2)
These values are stored as a list [x, y] in the val variable. By looking at the TMP102 datasheet, we see that temperature is 12 bits. When we read the two bytes that contain this reading, we need to remove the last 4 bits from the second byte. We also move the first byte over 4 bits:
temp_c = (val[0] << 4) | (val[1] >> 5)
In order to display negative numbers for the temperature, values from the TMP102 can come in the form of Two’s Complement. In this, the first bit of the 12-bit number determines if the value is positive or negative (0 for positive, 1 for negative). See this article to learn more about Two’s Complement.
To convert a Two’s Complement number to a negative number in Python, we check to see if the first bit is 0 or 1. If it is 0, then we just use the number as is (it’s positive!). If it’s a 1, we subtract the max negative number of the Two’s Complement (212=4096 in this case) from our number.
if (val & (1 << (bits - 1))) != 0:
val = val - (1 << bits)
Challenge: Change the CONFIG register so that the TMP102 updates its temperature reading 8 times per second (instead of 8). Additionally, print out the temperature in degrees Fahrenheit. Hint: see page 7 of the TMP102 datasheet to see which bits need to be changed in the CONFIG register.
import time
import smbus
i2c_ch = 1
# TMP102 address on the I2C bus
i2c_address = 0x48
# Register addresses
reg_temp = 0x00
reg_config = 0x01
# Calculate the 2's complement of a number
def twos_comp(val, bits):
if (val & (1 << (bits - 1))) != 0:
val = val - (1 << bits)
return val
# Read temperature registers and calculate Celsius
def read_temp():
# Read temperature registers
val = bus.read_i2c_block_data(i2c_address, reg_temp, 2)
temp_c = (val[0] << 4) | (val[1] >> 5)
# Convert to 2s complement (temperatures can be negative)
temp_c = twos_comp(temp_c, 12)
# Convert registers value to temperature (C)
temp_c = temp_c * 0.0625
return temp_c
# Initialize I2C (SMBus)
bus = smbus.SMBus(i2c_ch)
# Read the CONFIG register (2 bytes)
val = bus.read_i2c_block_data(i2c_address, reg_config, 2)
print("Old CONFIG:", val)
# Set to 8 Hz sampling (CR1, CR0 = 0b11)
val[1] = val[1] & 0b00111111
val[1] = val[1] | (0b11 << 6)
# Write 8 Hz sampling back to CONFIG
bus.write_i2c_block_data(i2c_address, reg_config, val)
# Read CONFIG to verify that we changed it
val = bus.read_i2c_block_data(i2c_address, reg_config, 2)
print("New CONFIG:", val)
# Print out temperature every second
while True:
temperature = read_temp()
temp_f = temperature * (9 / 5) + 32
print(round(temp_f, 2), "F")
time.sleep(1)
Experiment 5: File Reading and Writing
Let’s take our previous example (taking measurements from an I2C device) and log them to a file! This can be incredibly useful if you are trying to measure the temperature (light, humidity, wind speed, air pressure, people entering your room, or really anything) and want to see how it changes over the course of minutes, hours, or days.
We’ll turn our previous code into a module for use to use. Change the code in tmp102.py to the following:
tmp102.py
import smbus
# Module variables
i2c_ch = 1
bus = None
# TMP102 address on the I2C bus
i2c_address = 0x48
# Register addresses
reg_temp = 0x00
reg_config = 0x01
# Calculate the 2's complement of a number
def twos_comp(val, bits):
if (val & (1 << (bits - 1))) != 0:
val = val - (1 << bits)
return val
# Read temperature registers and calculate Celsius
def read_temp():
global bus
# Read temperature registers
val = bus.read_i2c_block_data(i2c_address, reg_temp, 2)
temp_c = (val[0] << 4) | (val[1] >> 5)
# Convert to 2s complement (temperatures can be negative)
temp_c = twos_comp(temp_c, 12)
# Convert registers value to temperature (C)
temp_c = temp_c * 0.0625
return temp_c
# Initialize communications with the TMP102
def init():
global bus
# Initialize I2C (SMBus)
bus = smbus.SMBus(i2c_ch)
# Read the CONFIG register (2 bytes)
val = bus.read_i2c_block_data(i2c_address, reg_config, 2)
# Set to 4 Hz sampling (CR1, CR0 = 0b10)
val[1] = val[1] & 0b00111111
val[1] = val[1] | (0b10 << 6)
# Write 4 Hz sampling back to CONFIG
bus.write_i2c_block_data(i2c_address, reg_config, val)
# Read CONFIG to verify that we changed it
val = bus.read_i2c_block_data(i2c_address, reg_config, 2)
Create a new file and enter the following code. Give the file a name such as templogger.py.
templogger.py
import time
import datetime
import tmp102
filename = "temp_log.csv"
# Create header row in new CSV file
csv = open(filename, 'w')
csv.write("Timestamp,Temperature\n")
csv.close
# Initialize communication with TMP102
tmp102.init()
# Sample temperature every second for 10 seconds
for t in range(0, 10):
# Construct CSV entry from timestamp and temperature
temp_c = str(round(tmp102.read_temp(), 2))
entry = str(datetime.datetime.now())
entry = entry + "," + temp_c + "\n"
# Log (append) entry into file
csv = open(filename, 'a')
try:
csv.write(entry)
finally:
csv.close()
# Wait 1 second before sampling temperature again
time.sleep(1)
# When all the writing has been completed, print the CSV contents
csv = open(filename, 'r')
print(csv.read())
csv.close()
Run the temperature logger with python templogger.py. You should see nothing happen for 10 seconds as the program reads the temperature once every second. For fun, try breathing on the temperature sensor to affect the data! After those 10 seconds, the collection should be complete, and the contents of temp_log.csv will be printed to the screen.
You can view the contents of the log by entering cat temp_log.csv into the console.
Code to Note:
If you remember from our Programming in Python section, we covered how to create modules. Modules let you reuse code that exist in other files. In our case, we put the “measure temperature” section of the tmp102.py code into a function definition so that we may call it from another file.
In our main program (templogger.py), we imported our own module with import tmp102. Notice that we left out the .py suffix–Python knows to look for a .py file.
You’ll also notice that whenever we open a file for reading or writing, we close it as soon as we can. It’s generally not good practice to open a file and leave it open. If your program or operating system crashes while the file is open, you could potentially corrupt the file (or worse, the whole filesystem).
For extra protection, we add the writing section in a try block:
try:
csv.write(entry)
finally:
csv.close()
Now, if something crashes while the program is trying to write to the file, an exception will be thrown and Python will automatically close the file before exiting.
You can also see that how we access a file is given by the second parameter in the open() function:
‘r’ - Read
‘w’ - Write (this will erase the original contents of the file)
‘a’ - Append (this will keep the original contents and add your additions at the end)
datetime.datetime.now() returns a datetime object, which we convert into a string with str(). Note that this is based on the local time of your Raspberry Pi. You can get a Coordinated Universal Time (UTC) date and timestamp with datetime.datetime.utcnow().
Challenge: Open the temperature log in a spreadsheet program and create a graph showing how the temperature changed over those 10 seconds (for extra credit, change the program to measure temperature over a longer period of time). Hint: Raspbian comes with LibreOffice Calc, a free spreadsheet program. If you use Calc, the Data > Text to Columns function might help you convert your date/timestamps into something usable.
Headless: If you are using the Raspberry Pi as a headless device, you may want to print the contents of the CSV to the screen, and copy-and-paste them into a spreadsheet program on your host computer.
Solution: There’s not really an “answer” to this. Get creative and show off your graphing skills!
Resources and Going Further
Hopefully, this tutorial has given you a starting point for your adventures with Python (and more specifically, how to control hardware with Python). If you would like to dig deeper into the Python language, here are some resources for you:
How much impact can the human body handle? This tutorial will teach you how to build your very own impact force monitor using a helmet, Raspberry Pi Zero, and accelerometer!
In this tutorial, we'll show you how to use the Flask framework for Python to send data from ESP8266 WiFi nodes to a Raspberry Pi over an internal WiFi network.
Are you 3D printing in a room at night with barely any light? That’s the problem that I came across when inspecting prints at SparkFun during odd hours. The light source behind me created a shadow over the print bed making it hard to see what was going on. In this tutorial, we will be using a LED strip to light up the print bed on a LulzBot 3D printer!
Required Materials
Note: This tutorial uses non-addressable LED strips but you could also customize your lighting by using an addressable LED strip of your choice. This will require additional components to get started.
To follow along with this tutorial, you will need the following materials. You may not need everything though depending on what you have. Add it to your cart, read through the guide, and adjust the cart as necessary.
Tools
You will need a screw driver, wire strippers, and diagonal cutters.
You Will Also Need
In addition to the materials listed above, you will need:
Electrical Tape
Hot Glue Gun and Glue Sticks
Hardware Hookup
⚡ Note: While the LED strip is labeled 12V, a 9V power supply was used so that the LEDs were not overwhelmingly bright. At 9V, it also did not dissipate as much heat.
The wiring is simple between the female barrel jack adapter and the non-addressable LED strip. Simply insert the black wire connecting the “12V”“ pin to the ”+“” of the barrel jack adapter’s screw terminal. Then insert the rest of the wires to the “–” screw terminal.
Click the image for a closer look.
Note: When testing the non-addressable LED strip, the pin labeled "G" was actually blue and the "B" was actually green. Depending on the manufacturer, the label may vary. Try testing the LED strip out with a power supply to determine see if the letter represents the color.
Hookup Table
Below is a hookup table that shows the connection between the female barrel jack adapter and non-addressable RGB LED strip.
Nonaddressable RGB LED Strip Pinout
Female DC Barrel Jack Adapter
+
12V
-
G
-
R
-
B
Connecting and Modifying the LED Strip
Note: This tutorial was written with the Taz 5 3D printer. However, you can use this as a guide to attach any type of LED strip to your 3D printer! You will just need to adjust the length as necessary depending on the size of your 3D printer frame and the density of LEDs on your strip.
We will be attaching the LEDs to the top of the frame. Make sure that the LEDs and wires are not in the way of any moving parts. Determine the length that you need by holding the LED strip up against the 3D printer's frame.
The length depends on the size of your printer’s frame. For the Lulzbot TAZ 3D printers, about 16x segments of the LED strip was used. For the Lulzbot Mini, about 12x segments seemed to be sufficient. Each segment consists of 3x LEDs between the exposed copper pads.
Click the image for a closer look.
Cut 4x segments off one end of the 1M non-addressable LED using the diagonal cutter. Relative to the 3D printer that I was using, I decided to remove the 4x segments from the right side of the LED strip.
Carefully strip extra wire insulation to expose more wire for the screw terminal. You may need to tin the tips of the wires by adding more solder. Then, insert the wires for R, G, and B, into barrel jack adapter's screw terminal labeled as “–”. Then tighten the screw with a Phillips head screw driver. Repeat for the black wire labeled as 12V on the “+” side.
Pull gently to verify that the wires are secure in the socket.
Insulate the exposed end of the LED strip with hot glue.
Connect a barrel jack power switch between barrel jack adapter and power supply.
Testing
Now would be a good time to check if the hardware was wired properly. Connect the power supply to a wall outlet. If the LEDs do not light up, flip the switch to the other side to see if the LED strip lights up.
Secure the LEDs
Attach the LED strip flush against the top of the 3D printer's frame and ensure that the wires are not in the way of the printable area. Since we want to illuminate the print bed, aim the LEDs toward the printer. While we could use the 3M adhesive that is on the back of the LED strip's clear jacket, I wanted to ensure that the adhesive did not fail during a print. Instead, the wires were secured by using electrical tape and wrapping the strip against the frame. Depending on the width of the tape, you may want to cut it down to prevent the LEDs from being blocked. You could use zip ties as well.
Tape was added as necessary on the enclosure; left, top, and right side of the frame as shown in the image below. Feel free to mount the power switch to another location so that you do not accidentally turn the power off to your 3D printer.
Hightlighted Areas with Electrical Tape
LED Strip Mounted to 3D Printer
Enjoy 3D Printing in the Light!
Insert the power supply into a wall outlet and flip the switch to power the lights when you need to check on your print!
Making It Better
Like all projects, you can always make it better and build upon the design. Here are few ideas to make it even more impressive by adding effects or interactive!
Add microcontroller and transistors to control each color with the press of a button to add an effect.
Getting started guide for the Large Digit display driver board. This tutorial explains how to solder the module (backpack) onto the back of the large 7-segment LED display and run example code from an Arduino.
Follow this tutorial to make your own light up PomPom headband! Try the beginner version if you are new to electronics or the advanced version if you have some more experience!
In this tutorial, we will be connecting a Mean Well LED switching power supply (5V/25W or 5V/40W) to an addressable LED strip controlled by an Arduino.
To follow along with this tutorial, you will need the following materials with the Mean Well 5V power supply. This is assuming that you are using the wall adapter cable for 120VAC. For the load, we will be using an addressable LED strip. You may not need everything though depending on what you have. Add it to your cart, read through the guide, and adjust the cart as necessary.
Depending on your setup, you may need a soldering iron, solder, and general soldering accessories. Otherwise, a screw terminal block and a screw driver is sufficient.
An overview of electric power, the rate of energy transfer. We'll talk definition of power, watts, equations, and power ratings. 1.21 gigawatts of tutorial fun!
Heads up! There are a few versions of the switching power supplies. We will be using the 5V series of the power supplies.
The Mean Well APV-35 and LPV-60 series power supplies were designed to power LEDs. They include wire pairs for the input (brown and blue) and output (red and black). The input voltage requires an AC power cable to be connected that is not included with the power supply. The APV-35-5 provides 5V with up to 5.0A. The LPV-60-5 provides 5V with up to 8.0A.
APV-35 Series
LPV-60 Series
Pinout
Mean Well Power Supply
Notes
ACL (Brown)
Input AC Voltage, Live/Hot wire
ACN (Blue)
Input AC Voltage, Neutral Wire, Wider Blade on Wall Outlet Side
V+ (Red)
Output Voltage (DC)
V- (GND, Black)
Output Ground (DC)
Hardware Assembly
Note: The tutorial follows the North American standard wiring at 120VAC for a polarized cable. If you are unsure about the standard wiring color in your region, please consult a certified eletrician to connect to the AC input voltage side.
Hookup Table
The following is the hookup table for connecting a wall adapter cable to a Mean Well power supply and then to your load. Ensure that the cable is not connected to a wall outlet when making the following connections between the cable and Mean Well power supply!
120VAC Outlet (North American Standard)
Mean Well Power Supply
Load (i.e. LED Strips)
Notes
LIVE/HOT Wire (Black)
ACL (Brown)
Input AC Voltage, Live/Hot wire
NEUTRAL Wire (White)
ACN (Blue)
Input AC Voltage, Neutral Wire, Wider Blade on Wall Outlet Side
V+ (Red)
5V
Output Voltage (DC)
V- (GND, Black)
GND
Output Ground (DC)
Connecting the AC Input Voltage w/ Screw Terminals
⚡ Warning! Make sure that your wires are secure and are rated to handle the current! Please be careful with the spade terminals when the cable is plugged into a wall outlet. Touching the terminals while powered could result in injury.
Before beginning, make sure the power cable is unplugged from the wall outlet. Carefully remove the plastic cover on the terminal block by wiggling it back and forth from the black housing.
Insert the hot wire's spade connector into a terminal block between the metal plates.
Tighten the screw. Pull wire gently to see if it is secure.
Repeat for the neutral wire's spade connector.
Connect the hot wire to the Mean Well's hot wire by inserting the wire between the metal plates and tightening the screw.
Remember to pull the wire gently to see if the connection is secure.
Repeat for the input neutral wire.
Connecting the DC Output Voltage w/ Screw Terminals
Connect your Mean Well power supply's output ground wire to one side of the terminal block.
Connect the output voltage's wire to another screw terminal.
Connect your load wires to the other side of the Mean Well output voltage.
Other Methods of Connecting to the Mean Well Power Supply
You can also splice the wires or use spade connectors depending on your preference. If you decide to connect with a spade connector, make sure that you are using the correct tool to properly crimp the connection. Needle nose pliers may not provide sufficient force to clamp the spade connector against the wires. Ensure that the power cable is unplugged from the wall outlet.
Remember to insulate your connections with electrical tape or heat shrink so that the connections are not exposed.
Once connected, make sure to test it out with a multimeter and surge protector before installing.
Testing the Output
Let's test the power supply using a multimeter to see if we connected everything properly! To safely test, we will be using alligator clips, probes, and a breadboard to measure the output voltage to see if we get our expected voltage. If you are confident in your connections, you can also connect a multimeter's alligator clips directly to the output. Insert the two prong cable into a surge protector that is turned OFF. When ready, flip the switch on your surge protector to the ON position to provide power.
Testing the APV-35 Series Output Voltage
Testing the LPV-60 Series Output Voltage
If you are measuring a voltage that is close to your Mean Well power supply's output voltage rating, you are good to go!
Adding a Load
Remove power and connect your load to the output. In this case, I decided to power an addressable LED strip using an Arduino and custom built shield.
For safety and installation, make sure to add electrical tape around the exposed input voltage side and mount the electronics securely in an enclosure.
When daisy chaining your addressable LED strips, there may be a voltage drop depending on the:
amount of LEDs connected
length of LED strip
how bright the LEDs are set
Below is an image of addressable LED strips daisy chained together and controlled by Arduino. The Arduino was programmed to turn on all the LEDs at full brightness using one 5V/25W power supply as an extreme case.
As you can see from the image below, the LEDs are not able to fully turn on after a certain distance due to the voltage drop. This is due to the increased resistance as you move further away from the power supply. You may notice that not all the colors are turned on or the strip becomes dim.
Warning: Turning on all the LEDs at full brightness is an extreme case. Higher density LED strips may not be able to handle the power and dissipate heat properly. It is recommended to use a lower brightness setting.
If you see voltage drops and the LED strip not properly turning on, you will need to connect the Mean Well's output between each LED strip's Vcc and GND after about 1 or 2 meters.
Warning: Make sure to use appropriate wire gauges that can handle the current. The example shown here was a temporary setup for testing. When using the LED strips for permanent installations, you will want to avoid using a breadboard and thin wires to power a large amount of LEDs.
As you can see from the image below, the LEDs throughout the strip are able to fully turn on when connecting power between each LED strip.
Again, turning on all the LEDs at full brightness is an extreme case. You may be able get away with injecting power after more than a few meters if your setup uses a lower brightness setting and sequencing the LEDs.
⚡ Using More Than One Power Supply Unit? If you are using more than one power supply for larger installations, it is recommended to disconnect the Vcc wire between each section's JST cable so that they are not conflicting. The data line(s) for data and ground for reference will still be connected.
⚡ Need More Power? You could also use a beefier power supply like the Mean Well 5V/20A with an adapter cable for your region.
Make bright, colorful displays using the 32x16, 32x32, and 32x64 RGB LED matrix panels. This hookup guide shows how to hook up these panels and control them with an Arduino.
Getting started guide for the Large Digit display driver board. This tutorial explains how to solder the module (backpack) onto the back of the large 7-segment LED display and run example code from an Arduino.
TL;DR: LoRaWAN™ is like cellphone towers for IoT that allow battery powered things to talk to the internet.
If you’re interested in the Internet of Things, you’ve probably heard of LoRa™ radios. LoRa™ is a fancy new kind of FSK (Frequency-shift keying) modulation that was developed by Semtech Corporation, it’s short for Long Range and it lives up to the name.
Because LoRa™ is just a modulation scheme, it can be used anywhere that you would use other types of packet radio. In fact, one of our favorite LoRa™ modules — the HopeRF RFM95W — is a direct drop-in replacement for their standard FSK radio module — the RFM69HCW— which makes it easy to upgrade your existing packet radio projects to take advantage of Semtech’s long-range, low-power technology.
One particular use of LoRa™ is uniquely exciting, though: LoRaWAN™
LoRaWAN™ is a network standard developed and maintained by the LoRa Alliance: an open association of collaborating members — mostly large tech companies — that’s designed to allow low-power devices to connect to each other and the internet using public gateways. The standard dictates that devices can move freely between gateways. Essentially, it’s like cellphone towers for IoT. The difference is that anyone can inexpensively own and operate a LoRaWAN™ gateway, so building the network is easy. And because anyone could theoretically operate a network server, it’s relatively resistant to monopoly.
The LoRa Alliance has laid out a template for LoRaWAN™ networks and this template consists of 4 parts: Nodes, Gateways, Network Servers and Application Servers.
Nodes
A LoRaWAN™ node is an endpoint device, such as a sensor or an actuator of some kind. A node can be up to 10km (6 miles!) away from a gateway in ideal conditions with the right radio modules. The connection between a node and a gateway is very low bandwidth — between 0.3 and 50 kbps — but it is bi-directional. Nodes need to be smart enough to encrypt and decrypt packets, handle network authentication and respect the duty cycle (we’ll talk about that in a minute) but these are tasks that can be easily achieved on super low-cost microcontroller hardware. The only other thing that a node needs to have is a LoRa™ radio and some kind of antenna.
Gateways
A LoRaWAN™ gateway— sometimes called a concentrator— is kind of like a cross between a cell tower and a WiFi router. It’s the bridge between the nodes and the internet. Because of the long range capabilities of LoRa™, a single gateway can theoretically service entire cities or hundreds of square kilometers. Ideally, however, a given node will be “heard” by multiple gateways to ensure the best network fidelity. Nodes don’t intrinsically know when they’ve been “heard” they just scream into the void, so it’s always good to have multiple gateways within range. If multiple gateways happen to get copies of the same packet, that will be taken care of upstream at the network server. Besides range, another limitation to any LoRaWAN™ gateway is the number of channels it has. The number of channels that a gateway has is the number of nodes that it can talk to at once. At first, this seems extremely limiting since many consumer gateways are around 8 channels, but then we introduce the concept of duty-cycle.
Nodes agree to adhere to a duty cycle limitation. This limitation is actually enforced by government regulation in many parts of the world in an effort to keep the airwaves open for everyone to use. A duty cycle is a measure of the fraction of time that a resource is in use. For instance, if you’re transmitting on a particular frequency for 2 seconds straight every 10 seconds, you’re operating at a 20% duty cycle. So what’s the duty cycle limitation for a LoRaWAN™ node? It depends on your local laws, but it’s probably 1%. For certain applications it may be as low as 0.1%, but this isn’t as low as it sounds. A 1% duty cycle represents almost 15 minutes of combined airtime per day, which far exceeds the fair usage policy of most free networks. And when you consider that a packet takes tens of milliseconds of airtime, these restrictions feel a lot more permissive. Also, by adhering to this duty cycle, we increase the number of nodes that can be serviced by a single gateway by a hundred times!
Network Servers
LoRaWAN™ nodes don’t know anything about the internet so the gateway can’t just turn LoRaWAN™ packets loose on the web and hope for the best, there needs to be a particular server that expects those packets and knows how to deal with them. This is the network server and — as the control center of the network — it has a lot of jobs to do. Its primary job is to direct packets between gateways and application servers. Since LoRaWAN™ allows for uplinks (messages to a server from a node) and downlinks (messages to a node from a server) and because the network server controls all gateways on the network there are a lot of packets to juggle.
Another thing that the network server does is to de-duplicate packets coming in from multiple gateways. Since any node might be within range of multiple gateways and there’s no hand-off when moving between them, the packets just get duplicated as each gateway sends its copy to the network server. The network server compares them and throws out identical packets. Finally, depending on the capabilities advertised by the network operator, a network server might be doing all kinds of other things like monitoring airtime usage and managing subscriptions. Some networks even offer localization for all nodes, triangulating them using Differential Time of Arrival techniques with multiple gateways.
The big thing to remember about the network server is that it does all of the behind-the-scenes work that makes a LoRaWAN™ network operate. Just like in the early days of cellular networks, not all gateways talk to the same network. LoRaWAN™ is only a network standard, not a network in and of itself. On top of this, the network server probably isn’t going to run any application-specific code to talk to your devices, but it will know where to send your packets so that your application-specific code can see them…
Application Servers
An application server is a server that’s connected to the network server (usually somewhere on the internet) that know specifically what to do with packets from a given node or type of node. For instance, if you have a website that shows the current weather conditions at a certain LoRaWAN™ weather station, the weather station is sending packets which are being relayed to the network server by one or more gateways but the network server isn’t interpreting that weather data or serving you the website… that stuff is happening on an application server. The application server and the node are both registered with the network server, so it knows to send packets from the weather station node to the weather station application server. Application servers can be anything from an IFTTT Webhook to a Raspberry Pi somewhere on the web. Sometimes an application server makes data available to browsers, sometimes it simply manipulates data and sends it back out to nodes over the network server.
“Okay, so how do I get in on this?”
There are a lot of LoRaWAN™ networks all over the world, some free to use and some paid. The largest free network today seems to be The Things Network, and it’s growing every day. Even if you’re not currently covered, you can set up your own gateway and join the network to help it grow!
The Things Network
“Building A Global Internet of Things Network Together.”
The Things Network is essentially the free, community-based, open source arm of The Things Industries— an integrated chain of products and services for developing enterprise IoT solutions. While The Things Industries makes their money selling hardware, software, services and consulting, they maintain The Things Network as a sort of tech demo and a tool for gaining market share. I know this is a cynical way to discuss a free open source network, but I like to put it in these terms because their service is so convenient and well maintained that it’s natural to wonder “what’s the catch?”
The Things Network (TTN) is a LoRaWAN™ network server with some extra bells and whistles. Registering a node or an application to the network is free and so is the network traffic — as long as you follow the fair use guidelines. Services are based on best effort, so there’s no guarantee on uptime or latency, but it’s free! The Things Network also encourages members to grow the network by making it incredibly easy to register a gateway. Simply connect your gateway hardware to the internet, open the TTN console, and follow the prompts. They sell their own branded gateway hardware, but they also provide documentation for registering your homebrew gateway. When you register a gateway, you also provide your gateway’s physical location and antenna placement so The Things Network can estimate network coverage and map that coverage for potential users.
To help grow the network, The Things Network even provides resources for finding, joining and starting regional “communities,” organizations dedicated to providing an entire city or area with network coverage. You can see a map of existing communities here, many large cities around the world already have total coverage!
Needless to say, we think The Things Network is a pretty cool idea. Also, since it’s free, it’s a great way to get started with LoRaWAN™. We’ll be using The Things Network to complete this tutorial, so if your city doesn’t have TTN coverage, you’ll need to set up a gateway of your own. You can buy one from The Things Network Marketplace or [make your own with an ESP8266 or a Raspberry Pi!
Heads up! I'll assume for the purposes of this tutorial that you're within range of a functioning gateway. If you're not, you'll need to set up one of your own and register it to The Things Network before continuing.
Registering your Node
Before you can program your first node, you’ll need to register a device on The Things Network. Registering allows the network to generate the necessary keys which you’ll then place in your code so that the network recognizes your device.
Heads up! From here on out, I’m going to assume that you have singed up for The Things Network and have access to the The Things Network Console. It’s free, to sign up you’ll just need an email address.
Once you’ve logged into the console, you’ll be presented with a screen like this:
You’ll notice that there’s no option to just add a node, that’s because The Things Network needs to know what application to associate with your device. Therefor, you’ll need to start by creating an application. Clicking on the Applications button will take you to a page that looks like this:
This would usually be a list of all your applications, but The Things Network very helpfully recognizes that you don’t have any applications yet and suggests that you add one. You can add an application either by clicking that link or clicking on “add application” in the upper right corner. Both of those links will take you to this page:
Give your application an Application ID, this is the name that The Things Network will use to distinguish it from the other applications. It can only contain lowercase letters, numbers, and dashes. The description field is for humans, so take a moment to write a sentence about what your app does. In this case, I’ve just written that it’s an example application. The EUI will be issued by the network, so there’s no need to type anything there. Finally, select the handler that you want your application to be registered to. Essentially, these are instances of the network server in different physical locations around the world. All applications will talk to all network servers, but to minimize latency, it’s best to select the handler closest to your gateway. I’m in Colorado, so I chose “ttn-handler-us-west,” which I assume is in California somewhere. Click the Add Application button and you’ll be taken to your freshly generated application console.
Now that you’ve created an application, you can register a device to it. Scroll down to the Devices section of the application page and you will see your current device count (which is none) as well as options to register a device and to manage devices. Click on register device.
This form is pretty similar to the Register an Application page, but you have a little less to do. Give your device a Device ID, the same rules apply as did with the Application ID. Then click the little crossed arrows beside the Device EUI field, this lets TTN know that you want them to generate an EUI for you. Now just click on the Register button at the bottom of the form.
And now you’ve been taken to your brand new device console. Under the section labeled Device Overview you’ll notice the Application ID and Device ID that you set earlier. Under Activation Method it will likely say “OTAA,” which stands for Over The Air Activation. This is a secure, transportable method of activating LoRaWAN devices, whereby the device uses a known application key to request new session keys whenever it wants to join the network. This is the preferred method for activating a production device, but for prototyping it’s usually easier to hard code the session keys into your device. In order to do this, we’ll need to set the activation method to “ABP” or Activation By Personalization. To do this, click on the Settings tab in the upper right-hand corner of the device page and you’ll be taken to the device settings menu. Partway down the page you should find an option to change the activation method.
Click on “ABP,” and then save your settings. When you return to the Overview page, you should now see some extra fields in the Device Overview:
We’ll need these numbers to program your Pro RF so leave this page up on your browser and let’s open up the Arduino IDE. It’s time to program the node!
We developed the SparkX Pro RF to be a small, lightweight LoRaWAN node. All you need to do is close two solder jumpers on the bottom of the board (labeled LoRaWAN) to connect the extra GPIO required by the LoRaWAN library. With that done, let’s get started!
Important! You'll need the Arduino IDE as well as SparkFun's Board Profiles in order to proceed. If this is your first time using Arduino, please review our tutorial on installing the Arduino IDE.
Get the LMIC-Arduino Library
For this example, we’re going to use a library written by Matthijs Kooijman which is a modified version of IBM’s LMIC (LoraMAC-in-C) library. You can download the library and install it manually from the GitHub Repository, or download it through the Arduino Library Manager. It’s currently called “IBM LMIC Framework” in the Arduino Library Manager, but that may change to “LMIC-Arduino” in the next release.
Once you have the library installed, you may need to edit the LMIC configuration file. Find your Arduino libraries folder and navigate to IBM_LMIC_framework/src/lmic/, you should find a file called config.h here, open it in any text editor and find the lines where CFG_us915 is defined. It should look like this:
language:c
//#define CFG_eu868 1
#define CFG_us915 1
// This is the SX1272/SX1273 radio, which is also used on the HopeRF
// RFM92 boards.
//#define CFG_sx1272_radio 1
// This is the SX1276/SX1277/SX1278/SX1279 radio, which is also used on
// the HopeRF RFM95 boards.
#define CFG_sx1276_radio 1
Since we’re using the 915MHz radio module, you need to make sure that the line #define CFG_us915 1 is not commented out and that the line #define CFG_eu868 1 is, by prepending // as shown above. Same goes for the radio type, we want #define CFG_sx1276_radio 1 and not #define CFG_sx1272_radio 1. with those changes made, save the config.h file and return to the Arduino IDE.
Edit the Example Code
With the library installed, you should now have the example code in your Examples menu. Let’s start with File>Examples>IBM LMIC framework>ttn-abp, this is the “ABP” or “Activation By Personalization” example code. In order to make it work with your application you’ll need to copy in some keys from the Device Overview page on your TTN Console, so flip back to the browser tab with the Device Overview page loaded up.
You’ll notice that, by default, the Network Session Key and App Session Key fields are obscured for security reasons. You can click the eye icon to show the code before copying it. Also, it will be easier to copy this into the example code if you click the <> button to show the codes in “C style”.
You will need to copy three separate numbers into your example code from this page: the Network Session Key, the App Session Key, and the Device Address. Here’s a diagram explaining which field on this page corresponds to which constant in the example code:
With these constants replaced in your example code, you can upload it to your Pro RF and watch for data to come pouring in!
Watch for Data
The example code is designed to send the string “Hello, World!” once every minute. This is actually a pretty large string for LoRaWAN and you shouldn’t allow the example code to run all day because it will eventually violate TTN’s Fair Usage Policy. But don’t worry too much about it, you can send a string the size of “Hello, World!” over 600 times in 24 hours before violating the policy.
To see the data arriving from your device, click on the Data tab in your device console. You should see a field labeled Application Data and after a minute or so, you’ll hopefully see your first packet come through:
Congratulations! You just sent your first LoRaWAN packet to The Things Network, your node device is working!
Decoding your Data
You may have noticed in the Application Data window that your payload is shown in raw bytes. In order to see “Hello, World!” encoded in ASCII, the way you sent it, you’ll need to decode the payload. The Things Network includes tools for doing this right in the console! Navigate to the Application Overview page for your application and click on the Payload Formats tab. This menu allows you to write functions which will be applied to all incoming packets for this application.
So let’s write our own decoder. We need to take the raw byte data and return a string that contains all of the characters corresponding to each byte. Take a look at this solution and then we’ll walk through it:
Decoder is a Javascript function that The Things Network has already set up for us, it takes two arguments called bytes, an array containing our payload, and port, the LoRaWAN “FPort” of the packet. FPort identifies the end application or service that the packet is intended for. Port 0 is reserved for MAC messages. We don’t need to know anything about the port number for our example.
We can return any value that we want from the Decoder function and it will appear alongside our payload in the Application Data window. In the example above, I’ve created a new property called “ASCII” which is equal to String.fromCharCode.apply(null, bytes). To break this down a little more, we’re returning a new String object called “ASCII,” and we’re using the Javascript apply() method to call fromCharCode() with the argument bytes and stuff the result into our new String. The fromCharCode() method simply steps through each byte in the array bytes (which, remember, contains our payload) and returns the ASCII character represented by that character code.
After copying the above code into your decoder function, scroll down and click the save payload functions button. Now return to the Application Data window and you should see that all packets received after the decoder function was changed now have a new property:
Our packet has been decoded! Excellent!
Using your Data
Okay, so now you have data coming in to your TTN application but what do you do with it now? Well, you have a few options:
APIs
The most basic endpoints for interacting with The Things Network programmatically are the TTN Handler APIs or “application programming interfaces”. There are two APIs, the Data API and the Application Manager API. The Data API allows you to send and receive messages, making it the most useful for most applications. You can interact with this API using the MQTT protocol. The Application Manager API is available directly through HTTP and lets you manage applications, gateways, and devices. It’s much more powerful than the Data API and is mostly intended to allow endpoint applications to perform device management.
SDKs
The Things Network has also created several Software Developer Kits (SDKs) which allow you to program your application without having to interact directly with the low level APIs. SDKs are available for several popular languages.
Integrations
Finally, the easiest way to access your data and put it to work is with The Things Network’s various platform integrations. Integrations allow you to pass your application data directly to another platform such as AWS IoT, Cayenne, EVRYTHNG, or IFTTT. From there, you can use those platforms to interact with your data.
Example: IFTTT Integration
As an example of how to use Integrations, let’s set up an IFTTT integration with our example application. If you’ve never used IFTTT, go ahead and create an account. IFTTT is a pretty cool platform that allows you to combine services from around the web to automate all kinds of tasks. These combinations of services are called “recipes” and they take the form of “If This happens, then make That happen,” and in our case, the This will be new data arriving in our application. First, head over to your TTN Application Console and click on the Integrations tab and then click “add integration”.
Find the “IFTTT Maker” integration in the list. It looks like this:
Clicking on that block will take you to the “Add Integration” form for this integration. The form has 6 fields:
Process ID
This is a name that TTN will use to keep track of this integration. You can name it whatever you want, I’m calling mine “my-new-integration”.
Event Name
This is the name of the IFTTT “recipe” that you want to trigger whenever the application receives new data. Go ahead and name this something, just remember what you named it so we can call your IFTTT recipe the same thing.
Key
This is your IFTTT Key. You can find this by going to the Maker Webhooks page on IFTTT and clicking on the Documentation button.
Value 1, Value 2, and Value 3
IFTTT Webhooks allow you to pass up to 3 values to your IFTTT recipe. In this case, let’s pass our decoded payload to IFTTT. To do this, we put the name of the property that we want to pass into the Value field, so type “ASCII” into Value 1.
Alright, take note of the Event Name that you set, click the Add integration button and let’s hop over to IFTTT. Get to the New Applet page and click on the +This in If +This Then That. You should now be looking at a list of all the services that IFTTT has triggers for… it’s a lot. Click the “Webhooks” service and choose the Receive a Web Request trigger. This is where you’ll enter the Event Name that we chose earlier when setting up the integration on TTN.
Click on Create Trigger and you’ll be brought back to the Make an Applet page. Now click on +That to add an action for the trigger we just created. There are all kinds of triggers available for IFTTT, but let’s do something simple for the purpose of this tutorial. Click on the Email service and select the Send Me an Email action. This will send you an email at the address that you used to sign up for IFTTT every time that new data comes in on your TTN application. You can even edit the body of the email. Notice that Value 1, Value 2, and Value 3 are ingredients in the email. Wherever you see that ingredient, it will be replaced with the appropriate value. In our case, Value 1 will be replaced with the value of ASCII. Click on Create Action and then Finish to start your new applet running.
Now open up your email and plug in your Pro RF. After a few minutes, you should get an email from IFTTT!
Welcome to the world of paper circuits - creating electronic projects directly on paper using simple components. This guide will walk you through building a simple circuit using copper tape, a 5mm LED, and a 3V coin cell battery.
Design and build time: 5-10 minutes
SparkFun offers two kits designed specifically for this paper circuit project. The main difference between the two kits is that the Classroom Pack includes batteries, four LED color options, and some spare parts. If using the SparkFun Paper Circuits Kit, you will need to provide or purchase your own 3V CR2032 20mm coin cell battery to follow along and complete the project.
In your paper circuits kit, you will find a plastic bag that includes:
Printed paper circuit template
12" piece of copper tape
Yellow 5mm LED
Binder Clip
You Will Also Need:
3V CR2032 Coin Cell Battery (included in the Classroom pack)
Scissors
Hole Punch
Clear Tape
Decorating supplies (markers, pens, crayons) if you’d like to color in the finished project
Don't have a SparkFun Paper Circuits Kit? You can follow along with this project using this wish list of individual pieces. You will need to print the template on cardstock and supply your own binder clip (available at local craft stores) to complete the project.
Downloadable Templates
Your kit includes a printed template. If you would like to choose a different template or make more projects, you can also download and print a PDF.
Right-click the link or images below, and choose “Save Link As” to download the templates to your computer. Each file has the circuit template on one side and a design on the other. Print on 8 ½” x 11” cardstock paper and cut out individual template (6 total). You will only need one to make the project.
Please note: Depending on your printer, your print may not align perfectly for double-sided printing at first without some setting adjustments. If needed, adjust your printer’s margins, or choose ‘Fit to Page’ in the print settings. We recommend printing on scrap paper first.
Suggested Reading
If you are brand new to working with electronics, here’s some helpful reading to check out:
Time to create a path for our electricity to flow through our circuit. In this project we will use copper tape wiring that connects each part of the circuit. Each template has numbered icons to help guide you in constructing the different parts of the circuit.
Take a look at the template and find the circle marked 1. Peel away a few inches of the paper backing from the copper tape and stick down along the shaded line to cover it.
This template includes a few turns - to keep a solid connection of copper around corners, we’ll be using a folding technique to press the tape into shape. Start by sticking the copper tape down until you reach the corner, then fold the tape backward on itself. Use a fingernail or pen to give it a good crease at the edge.
Then carefully move the tape down around the corner - you should see the fold forming - and press down flat against the paper. The neatness of the fold doesn’t matter that much, it will be covered by your pop up in the end.
Corners got you confused? Watch how it is done here (watch full screen to see the process a little better):
Finally, cut the tape when you reach the end of the printed line.
Repeat this process starting at the line marked 2.
Now we are done creating our copper tape paths. If you have extra tape, you can discard or save for another project.
Prepare and Place LED
Now that we’ve created the path for electricity to follow in the circuit, it’s time to add a light-emitting diode (LED) that will turn on when we power the project. Look for the circle marked 3 and the yellow LED illustration for where to place it on the template.
Pro Tip: Before adding the LED, fold the card in half along the line to save the hassle of trying to make a neat fold once there are components sticking up from the paper.
LEDs
The template has an LED symbol on it which shows its wires - let’s check it out. LEDs are polarized, which means they need to be connected in a certain orientation to function. The LED has two wires coming out of its colored bulb - notice one is longer than the other - this is the positive (+) side of the LED. The shorter side is the negative (-). We will connect these on the + and - sides of our copper tape paths. There is also a flat side on the bulb itself; this is the negative side.
We will need to do a little preparation in order for the LED to lay flat in our circuit. Using your fingers (or pliers), bend the longer wire (also known as its ‘leg’) of the LED flat. Be careful not to break the wire by bending back and forth over the same joint too many times.
Next, bend the shorter (-) leg of the LED flat as shown.
Once all bending is complete, place the LED on a table or flat surface to make sure it sits flat and upright. If not, make any adjustments now.
Tape Down LED
Next, line up the positive leg of the LED with the copper tape marked + and the negative with -. Use clear tape over the leads to hold down to the copper. If you have trouble telling the sides apart, check the base of the LED bulb - the flat edge is the negative (-) side.
Punch Hole
Before finishing the project, punch a hole for the LED to go through. You can use a hole punch or carefully cut with scissors.
Depending on how centered you placed the LED over the gap in the copper tape, you may need to slide or slightly move it. Simply peel up the clear tape and adjust to center the LED in the hole, then secure with some new tape. Fold the card over for a preview of where the LED will shine through on the template’s design.
Insert Battery
Once all the components are installed, it’s time to test our circuit by adding a battery. Place the battery inside the circle marked - with the labeled (marked + or with the battery name) facing up.
Carefully fold along the dotted line so that the copper tape inside the + circle rests on top of your battery, making a connection. You should see your LED light up once the copper tape touches the battery. Flip your project over and push the LED through the hole you punched to see it shine through the design.
Secure the battery in the project using a binder clip. If you don’t have a binder clip, a large paper clip can also work.
Troubleshooting
If your LED isn’t lighting up - check the tape connections. Use your nails or a pencil to make sure the LED’s wires are being held down to the copper tape and making contact.
Check the wires of the LED - double check that they weren’t accidentally broken while bending them or if the LED is installed with the + side to - side.
Check the battery - make sure it is sandwiched firmly between the top and bottom copper tape lines and that the top copper is not accidentally touching the bottom of the battery.
Decorate and Customize
Decorate your project by coloring in the design or adding stickers. You can also make it a wearable badge by adding an adhesive pin to the back of the design. To make it easier to color on the paper, unclip the binder clip and remove the battery so the paper will lay flat on a table.
If you have a spare binder clip, attach one to the other edge of the paper to create a stand for your artwork.
You can also attach a magnet to create a fridge decoration or a pin pack to wear your project around.
Resources and Going Further
Here are some other paper circuit projects to try out:
If you like the idea of a headless computer setup for your Raspberry Pi (i.e. one without a keyboard, mouse, or monitor) but want access to the full graphical desktop, then you’re in luck! By using a Virtual Network Computing (VNC) program, you can access a remote desktop over the network!
For schools and individuals that need to use the full desktop for certain applications (Scratch, creating your own graphical interface, etc.), using a VNC client to access your Raspberry Pi might be the way to go.
Using RealVNC to access the Raspberry Pi’s graphical desktop
The good news is that Raspbian (the recommended Raspberry Pi operating system) comes with RealVNC installed by default. The bad news is that we need to enable it using some other means.
Required Materials
To follow along with this tutorial, you will need a Raspberry Pi, power supply, and micro SD card. Note that no monitor, keyboard, or mouse is required!
Note: The Raspberry Pi Zero W should also work with this tutorial, if you want a smaller option for your project.
Optional Materials
All that being said, you can also use a keyboard, mouse, and monitor to enable the VNC server. Once you have enabled it, you will not need these accessories any more (unless you really want them).
Please note: If you have trouble seeing any of the images throughout this tutorial, feel free to click on it to get a better look!
Flashing the OS
The Raspberry Pi has several operating systems available for use, and beginners are often encouraged to use NOOBS to install the default Raspbian image. This guide will show you how to configure a headless version of Raspbian to be used with VNC. To do so, we’ll be following along with parts of the Headless Raspberry Pi Setup tutorial, but note that we’ll be using full Raspbian instead of Raspbian Lite, as we need the Linux X server that comes installed on the full version.
To start, download the latest version of Raspbian.
Note: This tutorial was created with Raspbian Stretch (version: June 2018). Using a different version may require performing different steps than what's shown in this tutorial. If you would like to download the June 2018 version of Raspbian, it can be found below.
To flash the image to your SD card, we recommend the program Etcher. Download and install it. Plug your SD card into your computer (using a microSD USB Reader if necessary), and run Etcher. Select your OS image file (no need to unzip it! just select your downloaded .zip file in Etcher), select your SD card reader, and click the Flash! button.
Enable VNC
You will need to interact with your Pi in order to turn on the VNC server. To do this, you have several options:
Connect a keyboard, mouse, and monitor. Click the Terminal icon on the top left of the desktop to open a terminal window.
By default, Raspbian should come with a VNC server (RealVNC) installed. If you are using another operating system, you might need to install RealVNC. With most flavors of Debian (e.g. Raspbian is built on top of Debian), you should be able to use apt-get to install RealVNC. In a terminal, enter the following:
You will need to go into the Raspberry Pi configuration tool to turn on the VNC server:
language:shell
sudo raspi-config
Select Interfacing Option, and then select VNC. On the next screen, select Yes, and press enter to save the changes.
Feel free to make any other changes you might like, including setting a new password and changing the keyboard layout.
Back in the raspi-config homescreen, press right arrow twice to select Finish and press enter.
Use VNC Over a Local Network
If your host computer is on the same local network (e.g. connected to the same WiFi or Ethernet network), then you can make a direct VNC connection to your Raspberry Pi. This method has several up sides: it’s the easier option, does not require signing up for a RealVNC account, and can be done on a closed network (i.e. one not connected to the Internet). The down side is that you must be on the same network to access your Pi (i.e. physically connected or through a VPN). This is a known as a direct connection.
If you want to to access your Raspberry Pi over the Internet, then see the next section.
Still in your Raspberry Pi’s terminal, enter the following command:
language:shell
ifconfig
Copy down the Raspberry Pi’s IP address, which is given as a series of 4 numbers next to inet. If you are connected over WiFi, this will appear under the wlan0 settings. If you are connected over Ethernet, this will appear under the eth0 settings.
On your host computer, head to the RealVNC Viewer downloads page to download the VNC client (known as VNC Viewer) for your operating system. Install it, accepting all the defaults.
Open VNC Viewer. At the top address bar, enter the IP address of the Raspberry Pi (once again, make sure your host computer and Pi are on the same network!).
Press enter, and click Continue when warned that “VNC Server not recognized.” You should be prompted with an Authentication window. If you did not change the login username and password for your Pi, your default login credentials are:
Username: pi
Password: raspberry
Heads up: it's highly recommended that you change your password! Anyone with access to your network could easily gain access to your Pi by trying the default username and password.
Once you successfully authenticate, you should be presented with your Raspberry Pi’s graphical desktop. Now, you can do everything remotely as if you were sitting in front of your Pi with a keyboard, mouse, and monitor! If you hover your mouse over the top part of the window, you should see a drop-down box appear, giving you access to the various RealVNC settings, including closing the session.
Use VNC Over the Internet
Using remote desktop on a local network is likely to offer a much faster and smoother experience, however, you can’t necessarily guarantee you will have access to that network directly. Luckily, RealVNC allows us to log in to our computer over the Internet!
The other good news is that we don’t have to go through the process of finding our Pi’s IP address.
Sign Up for a RealVNC Account
Note: Using RealVNC's cloud service means that we'll need to sign up for an account on their site. Also note that the cloud connection service is free for only non-commercial and educational purposes. See RealVNC's pricing guide for more details.
To start, sign up for an account (or sign in, if you already have one) on RealVNC’s site here. Head to your Account page, and click Activate under the Home version of VNC Connect.
Enable VNC Cloud Connection on the Pi
Note: You will need to have access to the graphical desktop for your Raspberry Pi for this next step. This can include using a keyboard, mouse, and monitor, or it can mean directly connecting with VNC over a local network. Only the Enterprise edition of RealVNC will allow you to enable the VNC cloud connection via command line.
Assuming you have VNC enabled on the Pi, click on the VNC logo in the top-right portion of the desktop. This will open the RealVNC settings window. In the top-right part of that window, click on the properties button, and select Licensing….
Leave Sign in to your RealVNC account selected, and click Next.
Enter your RealVNC email and password, and click Sign In. On the next screen, change the connectivity method to Direct and cloud connectivity.
Click Next. Review the settings and click Apply. If you get a pop-up warning you that permissions were granted without asking for a password, click Close. On the next screen, click Done.
Remotely Control the Raspberry Pi
On your host machine, download and install the RealVNC viewer. Open the application, and click the Sign in button in the top-right. Enter your email and password, and click Sign in.
On the right side, you should see an address book (previously used connections) and something showing your “Team” (computers available for a VNC cloud connection). Click on your Team, and you should see your VNC-ready Raspberry Pi listed.
Double-click on your Raspberry Pi to connect to it. You should see a pop-up window explaining that the VNC server on your Raspberry Pi has been verified. Click Continue. You should be prompted with an Authentication window. If you did not change the login username and password for your Pi, your default login credentials are:
Username: pi
Password: raspberry
Heads up: it's highly recommended that you change your password! Anyone with access to your network could easily gain access to your Pi by trying the default username and password.
Once you have been authenticated, you should be presented with your Raspberry Pi’s desktop. If you hover of the top part of the window, a drop-down should appear, giving you access to various settings for RealVNC, including a button to close the session.
Another slick feature is the ability to control your Raspberry Pi from your smartphone or tablet! Download the VNC Viewer app from the iTunes store or Google Play. Open the app, sign in, and connect to your Raspberry Pi!
Resources and Going Further
If you would like to dig deeper into the details of RealVNC, we recommend looking at its docs page.
While RealVNC comes pre-installed on Raspbian, there are other Linux-based VNC applications out there that you could try. Here are some alternatives:
If you don’t have any sort of network access for your Raspberry Pi, you can have your Pi host its own network by becoming a WiFi access point! This will allow you to connect to your Pi from another computer via SSH, VNC, etc. without relying on other networking equipment (e.g. routers).
This guide will show you how to configure a Raspberry Pi as an access point and connect it to your local Ethernet network to share Internet to other WiFi devices.
How much impact can the human body handle? This tutorial will teach you how to build your very own impact force monitor using a helmet, Raspberry Pi Zero, and accelerometer!
In this tutorial, we'll show you how to use the Flask framework for Python to send data from ESP8266 WiFi nodes to a Raspberry Pi over an internal WiFi network.
This tutorial will show you how to use a headless Raspberry Pi to flash hex files onto AVR microcontrollers as a stand-alone programmer. It also tells the story about production programming challenges, how SparkFun came to this solution, and all the lessons learned along the way.
When it comes to creating projects that glow, nothing beats Electroluminescent wire (or EL wire for short). LEDs are fun and all, but EL wire is what all the hip kids are using. Whether you just want to light up your bicycle for an evening cruise or you’re creating an entire light up costume for Burning Man, EL wire is a great solution.
In this tutorial, we will show you how to get started with EL wire. With the right parts, EL wire can be very easy to implement into any project!
Suggested Reading
If you aren’t familiar with the following concepts, we recommend checking out these tutorials before continuing. A general understanding of electricity is necessary to understand the theory behind EL Wire operation. EL Wire is powered with AC power. It’s not as dangerous as the electricity coming from you home outlets, but it does deserve the same respect. Depending on your setup, you will need to understand how a circuit works.
We can see electricity in action on our computers, lighting our houses, as lightning strikes in thunderstorms, but what is it? This is not an easy question, but this tutorial will shed some light on it!
Having a hard time seeing the videos? Try viewing them in a full screen mode.
How EL Works
EL wire (short for electroluminescent wire) is particularly useful for many reasons. Nevertheless, there are a few characteristics to keep in mind.
Note: For the sake of this tutorial, we will refer to EL wire, tape, and panels simply as EL wire unless stated otherwise.
EL Wire, Tape, Panel, Chasing Wire, Bendable Wire Forms
It comes in many different, shapes, and sizes. You can get it in wire (the most typical shape), tape, panels, and bendable form. All of these can be cut to any shape or size to achieve the desired effect. Just be sure to reseal the ends that have been cut.
Heads up! The benefit of bendable EL wire is that it allows you to articulate and mold your EL wire any way that you want! However, it is thicker than standard EL wire and chasing wire due to the additional bendable wire that runs alongside the phosphor wire.
Colors
EL also comes in many different colors. Below are a few options for standard EL wire.
Heads up! Depending on the manufacturer, the color may vary between standard EL wire, tape, panel, chasing wire, and bendable wire.
Flexible
EL wire is flexible to a point. It allows you to sew it into clothing, attach it to moving parts, and bend it into any shape you desire. EL wire is more flexible than using LED strips but you will want to avoid sharp bends.
EL tape and panels can also be used in e-textile projects. However,they are not as flexible as EL wire. They are better in projects when there is reinforced fabric to support the material.
EL requires less power to operate compared to using several LEDs for a project. EL is also great because it is cool to the touch, even after being on for hours. Hence why it is often seen in clothing applications. The EL does not heat up because, rather than heating an element to achieve an optical phenomenon, the glowing in EL comes from sending an electrical current through the material, which is comprised of semiconducting mixtures. The electrons flowing through the material create photons, which create the glowing that we see as a result.
Let It Glow… In a Dark Room
While EL has a nice glowing effect, it can be hard to see in the daylight or when there is light in a room. It would be better to use EL in low light conditions for the best effect.
EL in Bright Room
EL in Low Lighting
AC Power
Many people ask, “Can’t I just hook up EL wire to a battery?” The answer is, no! In order to operate EL wire properly, you must use AC (alternating current) power. This is similar to the power that comes out of your wall outlets at home, though outlets provide much more current than needed for EL wire. That’s where the inverter comes in!
The battery pack included in the EL starter kit and the chasing inverter are not just a battery holder. It houses an inverter as well. This inverter takes the DC (direct current) power produced by the batteries and turns it into AC. If you listen very closely to the inverter battery pack while it’s on, you will hear a slight hum, similar to what you would hear if you stand under power lines or close to transformer boxes. Compared to the battery pack however, the 3V and 12V inverters do not have a battery holder build in.
Note: SparkFun does not condone hanging out around high voltage areas.
With that, it’s important to mention that the AC power coming from the inverter is not enough to hurt or kill you. However, it is enough to give you a good shock. Be careful when handling EL products and any exposed circuits that are powered on the AC side. You can cut EL to any length or shape, but you must reseal the ends you cut. If you do not have an end cap for the EL wire, you can still seal the ends with hot glue or epoxy to seal cut wire. If you don’t reseal, you could end up getting a good jolt.
Anatomy of EL
EL consists of a few layers. Let's take a look at the anatomy of standard EL wire.
Colored, Clear PVC Sleeve– On the outside is a colored PVC sleeve. Depending on the manufacturer and color, this may be clear or translucent.
Clear PVC Sleeve– A second layer is yet another PVC sleeve. This sleeve is not as thick as the outer layer and is also clear.
Corona Wires– There are two thin wires that wrap around and extend to the end of the EL wire. These are very fine and can sometimes be referred to as angle wires. The wire pair is isolated from the center core.
Phosphor Coating– Applying AC power around the coating creates that nice glowing effect from the phosphor being excited. It also separates the corona wires and core.
Core Wire– At the center of EL wire is another wire.
On the left is a physical diagram of EL wire. On the right is a close up of white EL wire opened up. Regardless of the labels in the images, each component functions the same.
At a minimum, you'll need the following to power EL. We will be using the following components to get started. You may not need everything though depending on what you have. Add it to your cart, read through the guide, and adjust the cart as necessary.
EL Component– 1x-2x Strands of EL Wire of any color
Power– 2x AA Batteries is required to power the inverter pack.
Note: You may have opted for Chasing EL Wire instead of the standard solid colors. That's fine. This tutorial will focus on both kinds of EL Wire and how to use each. We'll discuss this in detail a little later.
Depending on your setup, you may also need the following depending on the inverter:
Wires(Optional)– Depending on your setup and inverter being used, you will need wires.
Insulation(Optional)– Electrical tape, heat shrink, hot glue, or epoxy to seal the exposed pins and wires connected to the AC side.
Note: SparkFun sells a kit with the parts available in the EL Wire Starter Kit with one strand of blue EL wire (3m).
Take the battery cover off by sliding in the direction indicated on the pack. Place your two AA batteries in the battery pack inverter, and put the cover back on. Plug in the male JST connector from your EL wire into one of the two female JST connectors on the inverter battery pack. Make sure there is a solid connection between the two.
Press the button on the case and your EL wire should illuminate. Press it again for a slow blinking effect, and press it once more for a fast blink. This inverter pack allows you to connect two EL products of your choice at a time. You can mix and match colors as well as shapes. You could have a red panel with blue wire, green tape with a pink wire, or two yellow and purple wires. The possibilities are endless!
Note: The following current readings were taken while the pack was connected to one or more of our 3m of EL wire:
Single Strand Always On: 190mA to 260mA
Single Strand Blinking: 90mA to 120mA
Two Strands Always On: ~300mA
Two Strands Blinking: ~150mA
Different color strands were used but the lengths were identical. Your mileage may vary.
Modifying the EL Inverter Battery Pack: We got a chance to play with these inverters and you can easily hack these to work with our Li-Po batteries (yep, fully charged at 4.2V). To get inside, you’ll need to remove a pair of screws (one is hiding under the CE sticker) and afterwards you’ll want to wrap things up with electrical tape to avoid getting shocked (it just tingles a little).
EL Inverter - 3V
We also have a 3V inverter. The wires are terminated at the end with JST PH connectors. These require a little bit more work to get started since they are designed to plug directly into our EL Escudo Dos or EL Sequencer. They are ideal for small EL displays.
This particular EL inverter accepts an input (on the red/black pair of wires for +Vcc and GND, respectively) anywhere from 2.5V-4.2V, so you can use them with batteries. Once powered, it can output up to 110VAC (on the black/black pair of wires) to drive EL wire.
⚡ Note: While that datasheet states that the input is between 2.5V-3.5V, we have tested the inverter with LiPo batteries. The 3V inverter can take up to 4.2V, so it's safe to run on a LiPo battery.
You can drive EL wires with them directly without using an EL Escudo Dos and EL Sequencer. However, you may need to re-terminate them, make an adapter, or possibly regulate the voltage down from your power supply. Below is a simple connection if you are just powering one strand for an installation using a 5V USB power supply, 3.3V FTDI to regulate the voltage down, M/M jumper wires, and one strand EL wire. The pins of the JST connector are small enough to be inserted into a JST connector.
Having a hard time seeing the circuit? Click on the image for a closer look.
You can even connect two strands together by making a parallel connection. For testing purposes, the circuit was placed on a breadboard. It should not matter what color of wire is connecting on the inverter's output since it is AC. The wire colors were connected together for consistency.
Having a hard time seeing the circuit? Click on the image for a closer look.
Tests with Different Lengths of EL Wire: Some real world testing revealed that this inverter can easily drive a 3m length of EL wire. When adding a second 3m length, the two get a bit dimmer, but still close to full brightness. At three 3m lengths, you can only tell all three wires are on if the room is dark. While the inverter can light up 4 strands, they are barely visible in normal light and very dim in the dark as well.
As you can see from the images below, connecting more EL wire in parallel with the 3V inverter will cause both EL wires to dim. The brightness can depend on the length of EL you are using, how you are wiring the EL, and the type of inverter.
One 3m EL Wire Lit Up
Two 3m EL Wires Lit Up But Dimmer
When you are done testing and integrating the EL wire in a project, make sure to seal any exposed EL wire or connections on the AC side. Electrical tape is a good option to secure the connection and insulate any exposed pins should you decide to continue using M/M jumper wire between an inverter and EL wire.
EL Inverter - 12V
When using several strands of EL, you may want to consider using the 12V inverter. The inverter consists of a barrel jack connector and a pair of wires terminated with a JST PH connector. These are ideal for the biggest, brightest display possible!
This particular EL inverter accepts an input via the 5.5mm x 2.1mm barrel jack connector. Simply connect a power source with a center positive barrel jack connector. The output (red/black pair or wires) was designed to connect to a mating JST connector on the EL Escudo Dos or EL Sequencer board. There is also a small switch on the side which allows you to switch between “on”, “blink”, and “off” settings.
Similar to the 3V inverter. You can drive EL wires with the 12V inverter directly. However, you may need to re-terminate them or make an adapter. Below is a simple connection if you are just powering one strand for an installation using a 12V power supply, M/M jumper wires, and one strand EL wire.
Having a hard time seeing the circuit? Click on the image for a closer look.
Again, you can connect two strands together by making a parallel connection. For testing purposes, the circuit was placed on a breadboard. It should not matter what color of wire is connecting on the inverter’s output since it is AC. The wire colors were connected together for consistency.
Having a hard time seeing the circuit? Click on the image for a closer look.
Remember to seal any exposed EL wire or connections on the AC side when you are done.
⚡ Remote/Mobile Applications: While that datasheet states that the input is between 11V-13V, you can power the 12V EL Inverter with 9V alkaline battery and adapter for remote applications. The EL might not be as bright as using a 12V power supply but you will not notice a significant difference in the dark with a few meters of EL.
Stress Tests with the 12V Inverter: The 12V inverter has been tested to run 8x strands of 3m EL wire using an EL Sequencer and a 12V power supply. Remember, as you increase the length or wire more in parallel, this will increase the load on the inverter causing the EL to dim.
How EL Chasing Wire Works
EL chasing wires work in a similar fashion to standard EL wire. Instead of one core wire, there are three thin wires coated in phosphor. Basically, you can think of three strands of standard EL wire smashed into one EL chasing wire. As a result, EL chasing wire requires 4x connections: three for each strand and one for common ground. By sequencing each strand inside by turning them on and off, we have a cascading or “chasing” effect.
Let's take a look at the anatomy of EL chasing wire.
Colored, Clear PVC Sleeve– On the outside is a colored PVC sleeve. Depending on the manufacturer and color, this may be clear or translucent.
Clear PVC Sleeve– A second layer is yet another PVC sleeve. This sleeve is not as thick as the outer layer and is also clear.
Corona Wires– There are two thin wires that wrap around and extend to the end of the EL wire. These are very fine and can sometimes be referred to as angle wires. The wire pair is isolated from the center core wires.
Phosphor Coating– Applying AC power around the coating creates that nice glowing effect from the phosphor being excited. It also separates the corona wires and core wires.
Core Wires– At the center of EL chasing wire are three thin wires coated and twisted at the center.
If you compare the size of EL standard wire and chasing wire, you will not notice the difference until you are modifying and repairing the core wire. As you can see, EL chasing wire uses three thin wires at the core.
Simply take the battery cover off by sliding it off, insert two AA batteries for power, slide the cover back on, and connect one EL chasing wire to the 4 pin mating connector. Press the button to turn on and begin sequencing the three strands of EL wire in one. Continue pressing the button to cycle through the three modes: slow, fast, and super-duper fast chase!
Stress Tests with the EL Chasing Inverter: It is recommended that these inverters only run up to 5m of EL wire, you may not have enough power to support longer lengths.
EL Wire Extension Cables
Need just a little more length in between your JST connector and EL? There are few methods to extend the wires to your EL. One method is splicing the wires leading to the EL material. Below is an example of extending the wires to an EL panel but it can be used for wires leading to any EL. For more information about how to extend the wires, head over to the Pokémon Go Patches with EL Panels: Adding EL Extension Cables tutorial.
Spliced Wires to Extend Wires to EL
Controlling and Sequencing EL
It’s as simple as that. However, if you are looking for more of a challenge, fear not. EL projects can become very complex, very quickly. You may have noticed that the effects are somewhat limited on the inverter, and both products hooked up behave the same when plugged into it.
Let’s say you want one color to blink while the other stays solid. You could purchase another inverter battery pack, or you could get one of the many boards out there that are specifically designed to work with EL products. SparkFun carries two such boards, the EL Sequencer and the EL Escudo Dos. Both of these boards are designed to handle many EL products hooked up to them at once, and there are many different effects you can create with both.
Thanks for checking out our tutorial! Now that you’ve successfully got your EL wire/panel/strip up and running, it’s time to incorporate it into your own project! For more information, check out the resources below:
PCA9548A and TCA9548A? The SparkX version of the Qwiic Mux breakout used the PCA9548A. The SparkFun red version uses the TCA9548A. Overall, both should be functionally the same with a few minor differences.
The Qwiic Mux - TCA9548A enables communication with multiple I2C devices that have the same address. The IC is simple to interface with and also has 8 configurable addresses of its own, this allows you to put 64 I2C buses on a single bus!
In this tutorial we’ll go over how to talk to sensors on different channels of your MUX. The application of this is pretty straightforward so things won’t get too fancy.
Required Materials
To get started, you’ll need a microcontroller to, well, control everything.
If you aren’t familiar with our new Qwiic system, we recommend reading here for an overview. We would also recommend taking a look at the following tutorials if you aren’t familiar with them.
What is the difference between the PCA9548A and TCA9548A? Very little. PCA is made by NXP, TCA is made by TI. PCA can operate from 2.3 to 5.5V, TCA can operate from 1.65 to 5.5V. Everything else is identical.
Let’s look over a few characteristics of the TCA9548A so we know a bit more about how it behaves.
Characteristic
Range
Operating Voltage
1.65V - 5.5V
Operating Temperature
-40 - 85° C
I2C Address
0x70 (default) up to 0x77 (see below table)
The Qwiic input for the Mux is located at the top-center of the board, labeled Main, highlighted in the image below. The outputs are then located on the left and right sides of the board and are numbered accordingly.
The onboard reset pin, highlighted below, is an active low input. Pulling reset low for at least 6 ns will restart the multiplexer.
The Qwiic Mux also allows you to change the last 3 bits of the address byte, allowing for 8 jumper selectable addresses if you happen to need to put more than one Mux on the same I2C port. The address can be changed by adding solder to any of the three ADR jumpers, shown in the image below.
The below table shows which jumpers must be soldered together to change to the corresponding address.
I2C Address
ADR2
ADR1
ADR0
0x70
Open
Open
Open
0x71
Open
Open
Closed
0x72
Open
Closed
Open
0x73
Open
Closed
Closed
0x74
Closed
Open
Open
0x75
Closed
Open
Closed
0x76
Closed
Closed
Open
0x77
Closed
Closed
Closed
If you want to remove the pullup resistors from the I2C bus, simply remove the solder from the jumper highlighted in the below image.
Hardware Assembly
If you haven’t yet assembled your Qwiic Shield, now would be the time to head on over to that tutorial.
With the shield assembled, SparkFun’s new Qwiic environment means that connecting the mux could not be easier. Just plug one end of the Qwiic cable into the Qwiic multiplexer breakout, the other into the Qwiic Shield of your choice and you’ll be ready to upload a sketch and figure out just how all those address sharing sensors are behaving. It seems like it’s too easy to use, but that’s why we made it that way!
Example Code
Note: This example assumes you are using the latest version of the Arduino IDE on your desktop. If this is your first time using Arduino, please review our tutorial on installing the Arduino IDE. If you have not previously installed an Arduino library, please check out our installation guide.
SparkFun has written some example code to enable and disable ports on the Qwiic Mux. Go ahead and download this example code here.
Warning! Make sure to have the Mux_Control.ino in the same folder when compiling the Example1-BasicReadings.ino sketch file. Otherwise, you may have issues uploading code.
Additionally, you will need to install the MMA8452Q Arduino library if you are using two MMA8452Q accelerometers. First, you’ll need the Sparkfun MMA8452Q Arduino library. You can obtain these libraries through the Arduino Library Manager. Search for Sparkfun MMA8452Q Accelerometer by Jim@SparkFun Electronics to install the latest version. If you prefer downloading the libraries from the GitHub repository and manually installing it, you can grab them here:
Opening Example1-BasicReadings will open two tabs in the Arduino IDE, the first example, and also Mux_Control. Let’s take a look under the hood of Mux_Control to get an idea of what’s going on. There are two functions here, boolean enableMuxPort(byte portNumber) and boolean disableMuxPort(byte portNumber) which is pretty much all we need to specify which channels we’d like to talk to on the Mux. If we have a sensor on channel 0, we simply call enableMuxPort(0) to open that channel on the multiplexer. Then we’ll take whatever reads and perform whatever actions we’d like to the sensor on that channel. Once finished, we have to call disableMuxPort(0) to close communication on that channel so we don’t accidentally perform actions on the sensor on that channel. The below example code shows how to read from two MMA8452Q accelerometers.
language:c
#include <Wire.h>
#include <SFE_MMA8452Q.h> //From: https://github.com/sparkfun/SparkFun_MMA8452Q_Arduino_Library
MMA8452Q accel;
#define NUMBER_OF_SENSORS 2
void setup()
{
Serial.begin(9600);
Serial.println("Qwiic Mux Shield Read Example");
Wire.begin();
//Initialize all the sensors
for (byte x = 0 ; x < NUMBER_OF_SENSORS ; x++)
{
enableMuxPort(x); //Tell mux to connect to port X
accel.init(); //Init the sensor connected to this port
disableMuxPort(x);
}
Serial.println("Mux Shield online");
}
void loop()
{
for (byte x = 0 ; x < NUMBER_OF_SENSORS ; x++)
{
enableMuxPort(x); //Tell mux to connect to this port, and this port only
if (accel.available())
{
accel.read();
Serial.print("Accel ");
Serial.print(x);
Serial.print(": ");
Serial.print(accel.cx, 2);
Serial.print("");
Serial.print(accel.cy, 2);
Serial.print("");
Serial.print(accel.cz, 2);
Serial.print("");
Serial.println(); // Print new line every time.
}
disableMuxPort(x); //Tell mux to disconnect from this port
}
delay(1); //Wait for next reading
}
With the example provided, you should be able to read two I2C sensors with the same address on the same bus! Try opening up the Arduino Serial Monitor set to 9600 baud in order to read the sensor values.
Resources and Going Further
Now that you’ve successfully got your Qwiic mux listening to all of those concurrent addresses, it’s time to incorporate it into your own project!
For more information, check out the resources below:
Sense various environmental conditions such as temperature, humidity, barometric pressure, eCO2 and tVOCs with the CCS811 and BME280 combo breakout board.
Get started interfacing your Qwiic enabled boards with your Raspberry Pi. This Qwiic connects the I2C bus (GND, 3.3V, SDA, and SCL) on your Raspberry Pi to an array of Qwiic connectors.
The Panasonic GRID-Eye (AMG88xx) 8x8 thermopile array serves as a functional low-resolution infrared camera. This means you have a square array of 64 pixels each capable of independent temperature detection. It’s like having thermal camera (or Predator’s vision), just in really low resolution.
Flex sensors are great for telling how bent something is in a project, but we’ve been running into issues with durability when using them in wearable applications like gloves. The Qwiic Flex Glove Controller isolates the weak point to allow for more permanent flex sensor applications. The board has an onboard ADS1015 ADC to I2C so we can get a whole bunch of analog inputs without touching our microcontroller’s ADC pins.
In this hookup guide, we’ll figure out how to pull values from our fingers as well as calibrate the sensor for our range of motion. We’ll also cover recommended placement and installation to implement these into gloves.
Required Materials
To get started, you’ll need a microcontroller to, well, control everything.
We would also recommend taking a look at the hookup guide for the Qwiic Shield if you haven’t already. Brushing up on your skills in I2C is also recommended, as all Qwiic sensors are I2C.
You’ll also most likely want to sew these boards into a wearable project, so if you’ve never picked up a needle and thread before, we’d recommend checking out a how-to on hand sewing.
Hardware Overview
Let’s look over a few characteristics of the ADS1015 so we know a bit more about how our glove controller behaves.
Characteristic
Range
Operating Voltage
2.0V - 5.5V
Operating Temperature
-40°C - 125°C
Resolution
12 bit
Sample Rate
128 Hz - 3.3 kHz
Current Consumption
150 µA (Typ.)
I2C Address
0x48 (default), 0x49, 0x4A, 0x4B
Pins
The characteristics of the available pins on the magnetometer are outlined in the table below.
Pin Label
Pin Function
Input/Output
Notes
3.3V
Power Supply
Input
Should be between 2.2V - 3.6V
GND
Ground
Input
0V/common voltage.
SDA
I2C Data Signal
Bi-directional
Bi-directional data line. Voltage should not exceed power supply (e.g. 3.3V).
SCL
I2C Clock Signal
Input
Master-controlled clock signal. Voltage should not exceed power supply (e.g. 3.3V).
Optional Features
The Qwiic Flex Glove controller has onboard I2C pull up resistors, which can be removed by removing the solder from the jumper highlighted below. Only remove this solder if you are using your own pullups on the I2C lines.
The I2C address of the board can be changed using the jumpers on the back of the board. The address selection pin is connected to the center pad of each jumper, the below table shows the addresses available when the address selection pin is tied to each of the 4 available pads.
Pin
Address
GND
0x48 (Default)
VCC
0x49
SDA
0x4A
SCL
0x4B
The location of the jumpers is shown in the below image.
The holes in the bottom corners of the board are used for sewing the board into the gloves of your choice.
Make sure you don’t crease the flex sensors as this will break the sensor!
Hardware Assembly
If you haven’t yet assembled your Qwiic Shield, now would be the time to head on over to that tutorial.
With the shield assembled, SparkFun’s new Qwiic environment means that connecting the sensor could not be easier. Just plug one end of the Qwiic cable into the Flex Glove Controller breakout, the other into the Qwiic Shield of your choice and you’ll be ready to upload a sketch and figure out how bent your fingers are. It seems like it’s too easy to use, but that’s why we made it that way!
You may want to integrate this board into some gloves, after all, that’s what it was originally designed for. If you’re looking to get sensors on 8 fingers, you’ll need 4 glove boards, and if you have 4 boards on the same IsC bus, you’ll need to use every address available to the ADS1015. So get started by changing the addresses of your boards so no two boards share the same address.
Now we want to attach our boards to our gloves. We’ve found it best to sandwich the board between two layers of gloves to keep the sensor flush with the finger. To accomplish this, we’ll sew the board to the outer layer of the inner glove. First, lay the glove out flat and place the board on the glove so that the ends of the flex sensors reach the tips of the fingers.
Once you have the sensor laid out on the glove, take a marker and mark the point where the sewing hole touches the glove.
Now simply sew the points to the available mounting holes on the sensor, The finished product should look like the below glove.
Now it’s time to hide the circuitry under a second glove. Go ahead and put the just the fingers of the second glove on, then slip the flex sensor down the gap between the the two layers of fabric. Sensors are shown at various states of this process in the image below.
Now just plug Qwiic cables to connect both boards together, then plug one board into your microcontroller so we can get readings from the glove.
Library Overview
Note: This example assumes you are using the latest version of the Arduino IDE on your desktop. If this is your first time using Arduino, please review our tutorial on installing the Arduino IDE. If you have not previously installed an Arduino library, please check out our installation guide.
Before we get into getting data from our flex sensors, let’s look at the available functions in the library. We’ve written a library to control the flex sensors. You can snag this library through the Arduino Library Manager. Search for SparkFun ADS1015 Arduino Library and you should be able to install the latest version. If you prefer manually downloading the libraries from the GitHub repository, you can grab them here:
Let’s get started by looking at the functions that set up the flex controller.
Setup and Settings
boolean begin(uint8_t deviceAddress = BNO080_DEFAULT_ADDRESS, TwoWire &wirePort = Wire);— By default use the default I2C address and use Wire port. Otherwise, pass in a custom I2C address and wire port.
uint16_t getAnalogData(uint8_t channel);— Returns the uncalibrated analog value from the sensor.
float getScaledAnalogData(uint8_t channel);— Returns a value between 0 and 1 based on calibration. Won’t work properly without first running calibrate()
void calibrate();— Used to calibrate the sensor and map the flexible range to values given by the user. While running calibration, simply flex each sensor to the minimum and maximum that it will be used in your project.
void setMode(uint16_t mode);— Set mode of the sensor. Mode 0 is continuous read mode, mode 1 is single-shot
uint16_t getMode();— Get’s the read mode of the ADS1015.
getCalibration(uint8_t channel, bool hiLo)— Get the high or low calibration value for a certain channel. if hiLo is true, getCalibration() will return the high calibration for the given channel.
setCalibration(uint8_t channel, bool hiLo, uint16_t value)— Sets the high or low calibration value of a channel without using the automatic calibration function. Allows for manual calibration.
resetCalibration()— Resets the calibration to 0.
void setGain(uint16_t gain);— Pass in different values for different gains
uint16_t getGain();— Get’s the gain of the ADS1015. This will return 16-bit hex value. The values and their corresponding gains are listed below.
0x0E00: ± 0.256V
0X0000: ± 6.144V
0X0200: ± 4.096V
0X0400: ± 2.048V
0X0600: ± 1.024V
0X0800: ± 0.512V
0X0A00: ± 0.256V
void setSampleRate(uint16_t sampleRate);— Sets the sample rate for the ADS1015, pass in the below 16-bit values to change to the corresponding sample rate.
0X0000: 128 Hz
0X0020: 250 Hz
0X0040: 490 Hz
0X0060: 920 Hz
0X0080: 1600 Hz
0X00A0: 2400 Hz
0X00C0: 3300 Hz
uint16_t getSampleRate();— Returns the sample rate according to the above list of sample rates.
Example Code
Now that we know how our library works, let’s go ahead and get started pulling values from our flex sensors.
Example 1 - Basic Readings
To get started with the first example, open up File>Examples>SparkFun ADS1015 Arduino Library>Example1_BasicReadings. In this example, we begin by creating an ADS1015 object called fingerSensor and then initializing our sensor object in the setup() loop. We then get the values from each finger by looping through and reading each channel on the ADS1015. The code for this is shown below.
Uploading this sketch and opening the Serial Monitor to 115200 bps will yield an output somewhat like the below image.
Single Sensor Output - click the image for a closer look
Example 2 - Setup hand
In this example, we’ll see how to setup an entire hand of flex sensors. To get started with this example, open up File>Examples>SparkFun ADS1015 Arduino Library>Example2_SetupHand. In this example, we create two ADS1015 objects, naming them indexSensor and pinkySensor to correspond with their locations on the glove. We also create an array with 4 spots to hold the data for the hand called hand. We then populate hand with values from each sensor. The code that accomplishes this is shown below.
Uploading this sketch and opening the Serial Monitor to 115200 bps will yield an output somewhat like the below image.
Full Glove Output - click the image for a closer look
Example 3 - Calibration
The second example will show us how to calibrate our flex sensor so we get 0 when our finger is closed and 1 when it is open. To get started, open up File>Examples>SparkFun ADS1015 Arduino Library>Example3_Calibration. In this example we will calibrate our sensor’s maximum and minimum values in order to find the range for our sensor.
language:c
#include <SparkFun_ADS1015_Arduino_Library.h>
#include <Wire.h>
ADS1015 fingerSensor;
void setup() {
Wire.begin();
Serial.begin(115200);
if (fingerSensor.begin(Wire, 100000, ADS1015_ADDRESS_GND) == false) {
Serial.println("Device not found. Check wiring.");
while (1);
}
Serial.println("Calibrating, send 'e' when finished");
}
void loop() {
uint8_t incoming;
do
{
fingerSensor.calibrate();
if(Serial.available())
{
incoming = Serial.read();
}
} while (incoming != 'e');
Serial.println("Calibrated");
for (int channel; channel < 2; channel++)
{
Serial.print("Channel ");
Serial.print(channel);
Serial.print(": ");
for (int hiLo = 0; hiLo < 2; hiLo++)
{
switch (hiLo)
{
case 0:
Serial.print("Low: ");
Serial.print(fingerSensor.getCalibration(channel, hiLo));
break;
case 1:
Serial.print(" High: ");
Serial.print(fingerSensor.getCalibration(channel, hiLo));
break;
}
}
Serial.println();
}
}
The sensor is initialized in the same manner as the first example, then our loop() begins calibrating the sensors. To calibrate the sensors, simply flex them to their maximum and minimum bend radii, then send an e over the Serial Monitor when you’re finished. This will save the current calibration and show you the values that have been saved. Uploading this sketch and opening the Serial Monitor to 115200 bps will yield an output somewhat like the below image once you’ve sent e and saved the calibration.
Calibration Output - click the image for a closer look
Example 4 - Calibrated Hand
You don’t necessarily want to calibrate your hand every single time you put on a glove with the flex controllers built in, so let’s figure out how to manually set our calibration if we know it already. To get started, open up File>Examples>SparkFun ADS1015 Arduino Library>Example4_ManualCalibration. We can see in the preamble to our code that we have an array of all of our calibration values, which were obtained using the previous example sketch. We then use a loop in the setup function along with our setCalibration() function to set the individual calibration values. The code for this is shown below.
language:c
#include <SparkFun_ADS1015_Arduino_Library.h>
#include <Wire.h>
ADS1015 pinkySensor;
ADS1015 indexSensor;
float hand[4] = {0, 0, 0, 0};
uint16_t handCalibration[4][2] = {
//{hi , low}
{722, 1080},//index
{600, 980},//middle
{680, 900},//ring
{736, 907} //pinky
};
void setup() {
Wire.begin();
Serial.begin(115200);
//Begin our finger sensors, change addresses as needed.
if (pinkySensor.begin(Wire, 100000, ADS1015_ADDRESS_SDA) == false)
{
Serial.println("Pinky not found. Check wiring.");
while (1);
}
if (indexSensor.begin(Wire, 100000, ADS1015_ADDRESS_GND) == false)
{
Serial.println("Index not found. Check wiring.");
while (1);
}
//Set the calibration values for the hand.
for (int channel; channel < 2; channel++)
{
for (int hiLo = 0; hiLo < 2; hiLo++)
{
indexSensor.setCalibration(channel, hiLo, handCalibration[channel][hiLo]);
pinkySensor.setCalibration(channel, hiLo, handCalibration[channel + 2][hiLo]);
}
Serial.println();
}
}
void loop() {
for (int channel = 0; channel < 2; channel++)
{
//Keep in mind that getScaledAnalogData returns a float
hand[channel] = indexSensor.getScaledAnalogData(channel);
hand[channel + 2] = pinkySensor.getScaledAnalogData(channel);
}
for (int finger = 0; finger < 4; finger++)
{
Serial.print(finger);
Serial.print(": ");
Serial.print(hand[finger]);
Serial.print("");
}
Serial.println();
}
Uploading this sketch and opening the serial monitor will show a stream of calibrated values. Use these to scale any other variable you’d like in your project.
Calibrated Hand Output - click the image for a closer look
Resources and Going Further
Now that you’ve successfully got your Qwiic Flex Glove Controller up and running, it’s time to incorporate it into your own project!
For more information, check out the resources below:
It's now easier than ever to measure and characterize how different materials absorb and reflect different wavelengths of light. The AS726X spectral sensors allow you to detect wavelengths in the visible range (VIS) and near infrared range (NIR)!
Sense various environmental conditions such as temperature, humidity, barometric pressure, eCO2 and tVOCs with the CCS811 and BME280 combo breakout board.
Buttons, slide switches, and reed switches are electronic components you can use to control a project, turn it on or off, or trigger behaviors in the code of a program. This guide will provide an overview of the options available in the LilyPad sewable electronics line and some examples of using them in a project.
A tutorial on electronics' most overlooked and underappreciated component: the switch! Here we explain the difference between momentary and maintained switches and what all those acronyms (NO, NC, SPDT, SPST, ...) stand for.
An introduction to the LilyPad ecosystem - a set of sewable electronic pieces designed to help you build soft, sewable, interactive e-textile projects.
Buttons and switches are electronic components that control the flow of current in a circuit. They can act as a simple gateway to light up an LED or as inputs for a microcontroller. Buttons are considered a type of switch, often with a momentary push actuation. We’ll refer to both as switches in this section.
Switch States
To change a switch from one state to another requires a physical action, often a flip, slide, or push. This is called actuating the switch. Different types of switches have different actuation methods. In the LilyPad line you can activate switches by sliding, pushing, and even using a magnet to trigger.
Maintained vs Momentary Switches
Switches that stay in one state until changed are called maintained switches. Examples of some common maintained switches are light switches, on/off switches on devices, and toggle switches. The LilyPad Slide Switch is an example of a maintained switch.
A momentary switch is only active when being actuated. Push buttons are a common example of a momentary switch. Other momentary switches you may find around you are the keys on your keyboard - a letter is only typed when the key is pressed. The LilyPad Button is an example of a momentary switch.
LilyPad Slide Switch
The LilyPad Slide Switch has a small switch labeled ON/OFF. When moved to the OFF position, parts inside the switch move away from each other and open the circuit (disconnecting it). No current will flow through the switch to the components connected to its sew tabs. When the toggle switch is moved to the ON position, the two sew tabs on the switch are connected, allowing current to flow through and close the circuit.
Using a LilyPad Slide Switch on the LilyPad ProtoSnap Plus
Examples
Slide switches can be used to control individual LEDs in an e-sewing project. Here’s an example of using three slide switches connnected with conductive thread to control each color channel on a LilyPad TriColor LED.
Simple color mixing circuit using LilyPad Switches connected to each color tab of the tri-color LED.
A slide switch can also be used to turn off an element in a project, such as a buzzer or indicator while debugging or when you want a bit of peace and quiet. The example below shows a slide switch sewn in a LilyPad Buzzer. This allows the other features of the project to still function while disconnecting power to the buzzer.
You can also connect the slide switch to a LilyPad Arduino and read its state in your programs.
Here’s an example of using a LilyPad Button and LilyPad Switch in a project on the LilyPad ProtoSnap Plus to control LEDs.
This wearable dice project tutorial utilizes seven slide switches to select from a 4, 6, 8, 10, 12, 20, and 100 side virtual dice for gaming.
A playful, geeky tutorial for a leather bracer that uses a LilyPad Arduino, LilyPad accelerometer, and seven segment display to roll virtual 4, 6, 8, 10, 12, 20, and 100 side dice for gaming.
The LilyPad Reed Switch is another kind of switch available in the LilyPad line. Unlike the other LilyPad switch offerings, the reed switch does not require you to touch the board to activate it. Inside the reed switch are two thin pieces of metal that are pulled in contact with each other when exposed to a magnetic field.
Read more about how to use it in the full hookup guide.
In this episode of Electricute, Dia and Nick build an interactive Krampus stocking using the LilyPad Reed Switch and a magnet.
LilyPad Button
The LilyPad Button Board is also a type of switch. When you press the button in the middle of the board, it connects the two sew tabs and allows current to flow through. When you let go of the button, the connection is opened again, and the button springs back into place. This button is an example of a momentary switch.
Pressing the LilyPad Button on the E-Sewing ProtoSnap
Examples
This stuffed creature uses both a slide switch and button to control LEDs embedded in it.
Here’s an example of a button being used as an input connected to the LilyMini microcontroller to switch LED modes.
Resources and Going Further
In addition to using LilyPad pieces to add interactivity to your projects, you can also create your own custom buttons and switches. Check out these blog posts for more:
Ready to add some interactivity to a project using LilyPad Arduino? Check out the ProtoSnap Plus Activity Guide for code examples to follow along with.
Learn how to program in Arduino with the LilyPad ProtoSnap Plus. This guide includes 10 example activities that use the pre-wired LilyPad boards on the LilyPad ProtoSnap Plus.
A fun project that uses the LilyPad MP3 trigger. This apron will dispense helpful kitchen advice and humor from the host of My Drunk Kitchen, Hannah Harto!
Interested in getting into LilyPad? Or maybe it's Arduino that tickles your fancy? Just want to add a little white-blinky-LED zest to your vest? All of the above? The ProtoSnap LilyPad Simple is a great tool to explore any of these subjects.
Python is a wonderful high-level programming language that lets us quickly capture data, perform calculations, and even make simple drawings, such as graphs. Several graphical libraries are available for us to use, but we will be focusing on matplotlib in this guide. Matplotlib was created as a plotting tool to rival those found in other software packages, such as MATLAB. Creating 2D graphs to demonstrate mathematical concepts, visualize statistics, or monitor sensor data can be accomplished in just a few lines of code with matplotlib.
The Raspberry Pi is a great platform for connecting sensors (thanks to the exposed GPIO pins), collecting data via Python, and displaying live plots on a monitor.
Notice: This tutorial was written with Raspbian version "June 2018" and Python version 3.5.3. Other versions may affect how some of the steps in this guide are performed.
Required Materials
To work through the activities in this tutorial, you will need a few pieces of hardware:
Optional Materials
You have several options when it comes to working with the Raspberry Pi. Most commonly, the Pi is used as a standalone computer, which requires a monitor, keyboard, and mouse (listed below). To save on costs, the Pi can also be used as a headless computer (without a monitor, keyboard, and mouse).
Note that for this tutorial, you will need access to the Raspbian (or other Linux) graphical interface (known as the desktop). As a result, the two recommended ways to interact with your Pi is through a monitor, keyboard, and mouse or by using Virtual Network Computing (VNC).
At the bare minimum, you need a breadboard and some jumper wires to connect the Pi to the TMP102 sensor. However, the Pi Wedge and some M/M jumper wires may make prototyping easier.
Please note: If you have trouble seeing any of the images throughout this tutorial, feel free to click on it to get a better look!
Prepare Your Pi
To begin, you will need to flash an image of the Raspbian operating system (OS) onto an SD card (if you have not done so already). You have a couple of options:
In the Python Programming Tutorial, follow the Install the OS section, making sure to go with the Full Desktop Setup
Once you have installed the OS for your Raspberry Pi, follow the steps in Configure Your Pi. If given the choice, choose the steps that relate to the Full Desktop setup.
Alias Python and Pip
We will be using Python 3 in this tutorial. At the time of writing, Python 2 was still the default version with Raspbian, which means that we will need to tell Linux that the command python should execute Python version 3.
Note: If you have already performed these steps in the Configure Your Pi portion previously, feel free to skip this part.
Open a terminal and enter the following command to edit the .bashrc file:
language:shell
nano ~/.bashrc
Scroll down to the bottom of the file, and add the following (if they are not already present):
language:shell
alias python='/usr/bin/python3'
alias pip=pip3
Exit out of nano with ctrl+x, press y, and press enter. Run the .bashrc script with:
language:shell
source ~/.bashrc
You can check the versions of Python and pip with:
language:shell
python --version
pip --version
Both should tell you the they are using a version of Python 3 (e.g. 3.5.3).
Enable I2C
By default, Raspbian disables the I2C port, which we’ll need to talk to the TMP102.
Note: If you have already performed these steps in the Configure Your Pi portion previously, feel free to skip this part.
Bring up the Raspberry Pi configuration menu:
language:shell
sudo raspi-config
If asked to enter a password, type in the password you set for your Raspberry Pi. If you did not change it, the default password is raspberry.
Select 5 Interfacing Options.
Select P5 I2C, select yes on the following screen, and press enter to enable the I2C port.
Back on the main screen, highlight Finish and press enter. A reboot is not necessary if you did not change any other options.
Install Dependencies
Like any good Linux project, we need to install a number of dependencies and libraries in order to get matplotlib to run properly. Make sure you have an Internet connection and in a terminal, enter the following commands. You may need to wait several minutes while the various packages are downloaded and installed.
You are now ready to build your circuit and make some graphs!
Hardware Assembly
In this guide, we will read temperature data from a TMP102 temperature sensor and plot it in various ways using matplotlib. After a brief introduction to matplotlib, we will capture data before plotting it, then we’ll plot temperature in real time as it is read, and finally, we’ll show you how to speed up the plotting animation if you want to show faster trends.
To begin, you’ll need to connect the TMP102 to the Raspberry Pi, either directly or through a Pi Wedge. We recommend soldering headers onto the TMP102 if you plan to use a breadboard. If you need help, this tutorial shows you the basics of soldering.
Connect the TMP102 as shown in one of the following diagrams (with or without the Pi Wedge). If you need help with layout of the Pi’s GPIO headers, refer to this guide.
Connecting through a Pi Wedge:
Click the image for a closer look.
Connecting directly to the Raspberry Pi:
Click the image for a closer look.
Introduction to Matplotlib
Matplotlib is a wonderful tool for creating quick and professional graphs with Python. It operates very similarly to the MATLAB plotting tools, so if you are familiar with MATLAB, matplotlib is easy to pick up.
The Absolute Basics
The easiest way to make a graph is to use the pyplot module within matplotlib. We can provide 2 lists of numbers to pyplot, and it will create a graph with them. Note that the 2 lists need to have the same length (same number of elements). The first list is a collection of numbers in the X domain, and the second is a collection of numbers in the Y range.
Use your favorite text editor or Python IDE to enter the following code:
Save the program (e.g. as myplot.py). Run it with:
language:shell
python myplot.py
You should see the graph appear in a new window.
While the basic line graph is likely the most used graph, matplotlib is also capable of plotting other types of graphs, including bar, histogram, scatter, and pie (among others).
If you want to save the image, you can click on the Save icon in the plot’s window.
Note that the points from the xs and ys lists are related to each other. The fist element of xs (i.e. xs[0]) and the first element of ys (i.e. ys[0]) make up the first point, (0, 1) in this instance. Pyplot automatically draws a line between one point and the next in the series.
Formatting
Like any good graph-creation tool, pyplot lets you change the formatting of your graphs with legends, titles, and labels. Let’s create a new plot:
language:python
import matplotlib.pyplot as plt
import math
# Create sinewaves with sine and cosine
xs = [i / 5.0 for i in range(0, 50)]
y1s = [math.sin(x) for x in xs]
y2s = [math.cos(x) for x in xs]
# Plot both sinewaves on the same graph
plt.plot(xs, y1s, 'r^', label='sin(x)')
plt.plot(xs, y2s, 'b--', label='cos(x)')
# Adjust the axes' limits: [xmin, xmax, ymin, ymax]
plt.axis([-1, 11, -1.5, 1.5])
# Give the graph a title and axis labels
plt.title('My Sinewaves')
plt.xlabel('Radians')
plt.ylabel('Value')
# Show a legend
plt.legend()
# Save the image
plt.savefig('sinewaves.png')
# Draw to the screen
plt.show()
Save and run this code. You should see two different sinewaves overlapping each other.
Here, you might notice a few differences from the first plot. We are creating two different plots, and by calling plt.plot() twice, we can draw those plots on the same set of axes (i.e. on the same graph). To create those plots, we use the range() function to generate numbers from 0 to 50 (exclusive) and divide each number by 5. This creates a series of numbers from 0 to 10 equally spaced by 0.2.
Note: As it turns out, Python does not have many easy ways to interact with lists or arrays. The numpy package is a great way to work with arrays, and it offers an impressive speed boost over native Python methods. If you are looking for a free MATLAB replacement, numpy is a good contender. When you installed the matplotlib package, numpy was installed by default!
When it comes to formatting the graph, we have plenty of options at our fingertips. We can “zoom” by setting the limit on the axes with plt.axis(). We’re able to add a title and axis labels. We can also show a legend by adding label= as an argument in the plt.plot() function call.
Additionally, if you look at our call to plt.plot(), you’ll notice that we can specify how we want the plots to look with the third parameter. 'r^' says to make the points red (r) and appear as triangles (^). 'b--' says to make the plot blue (b) and use a dashed line (–). See the Notes section in the plot documentation to see the various options for formatting strings.
Finally, we were able to create the image above by calling plt.savefig(). This saves the current figure as an image in the same directory as the Python code (and we can do it programmatically!)
Refer to the pyplot documentation to see all the available functions for plotting and formatting.
Subplots
Sometimes, you may want to display multiple plots on a single image (or figure). To accomplish this, we can use supblots:
language:python
import matplotlib.pyplot as plt
import math
# Create sinewaves with sine and cosine
xs = [i / 5.0 for i in range(0, 50)]
y1s = [math.sin(x) for x in xs]
y2s = [math.cos(x) for x in xs]
# Explicitly create our figure and subplots
fig = plt.figure()
ax1 = fig.add_subplot(2, 1, 1)
ax2 = fig.add_subplot(2, 1, 2)
# Draw our sinewaves on the different subplots
ax1.plot(xs, y1s)
ax2.plot(xs, y2s)
# Adding labels to subplots is a little different
ax1.set_title('sin(x)')
ax1.set_xlabel('Radians')
ax1.set_ylabel('Value')
ax2.set_title('cos(x)')
ax2.set_xlabel('Radians')
ax2.set_ylabel('Value')
# We can use the subplots_adjust function to change the space between subplots
plt.subplots_adjust(hspace=0.6)
# Draw all the plots!
plt.show()
Run this, and you should get two sinewaves, each in its own subplot.
Up to this point, we had been calling plt.plot() to draw on the canvas. In reality, this is a shortcut to create a figure object (the background where we draw our plots) and then create a set of axes on a single plot on that figure. To create subplots, we need to explicitly create that figure so that we get a handle to it. We can then use the fig handle to create subplots on the figure.
The add_subplot() function must be given a series of numbers (or a 3-digit integer) representing the height, width, and position of the subplot to create. (2, 1, 1) says to create a 2x1 subplot grid (2 high, 1 across) and return a handle to the first subplot (the one on top). (2, 1, 2) similarly says that in the same 2x1 gird, return a handle to the second subplot (the one on bottom). We name these handles ax1 and ax2 (for axes 1 and 2).
We then draw our sinewaves on the axes directly (rather than using the shortcut plt.plot()). We also add labels to everything. Without any adjustments, the labels would be hidden by the axes by default. To account for this, we set the hspace parameter, which controls the amount of height between subplots. Feel free to play around with the other parameters in subplots_adjust to see how they work.
Plot Sensor Data
In the Python Programming Tutorial: Getting Started with the Raspberry Pi, the final example shows how to sample temperature data from the TMP102 once per second over 10 seconds and then save that information to a comma separated value (csv) file. To start plotting sensor data, let’s modify that example to collect data over 10 seconds and then plot it (instead of saving it to a file).
TMP102 Module
In order to simplify I2C reading and writing to the TMP102, we will create our own TMP102 Python module that we can load into each of our programs. Open a new file named tmp102.py:
language:shell
nano tmp102.py
Copy in the following Python code:
language:python
import smbus
# Module variables
i2c_ch = 1
bus = None
# TMP102 address on the I2C bus
i2c_address = 0x48
# Register addresses
reg_temp = 0x00
reg_config = 0x01
# Calculate the 2's complement of a number
def twos_comp(val, bits):
if (val & (1 << (bits - 1))) != 0:
val = val - (1 << bits)
return val
# Read temperature registers and calculate Celsius
def read_temp():
global bus
# Read temperature registers
val = bus.read_i2c_block_data(i2c_address, reg_temp, 2)
temp_c = (val[0] << 4) | (val[1] >> 5)
# Convert to 2s complement (temperatures can be negative)
temp_c = twos_comp(temp_c, 12)
# Convert registers value to temperature (C)
temp_c = temp_c * 0.0625
return temp_c
# Initialize communications with the TMP102
def init():
global bus
# Initialize I2C (SMBus)
bus = smbus.SMBus(i2c_ch)
# Read the CONFIG register (2 bytes)
val = bus.read_i2c_block_data(i2c_address, reg_config, 2)
# Set to 4 Hz sampling (CR1, CR0 = 0b10)
val[1] = val[1] & 0b00111111
val[1] = val[1] | (0b10 << 6)
# Write 4 Hz sampling back to CONFIG
bus.write_i2c_block_data(i2c_address, reg_config, val)
# Read CONFIG to verify that we changed it
val = bus.read_i2c_block_data(i2c_address, reg_config, 2)
Save the code with ctrl + x, press y, and press enter. This allows us to call the init() and read_temp() functions to easily get temperature (in Celsius) from the TMP102.
Temperature Logging and Graphing
In the same directory as the tmp102.py file, create a new file (using your favorite editor), and paste in the following code:
language:python
import time
import datetime as dt
import matplotlib.pyplot as plt
import tmp102
# Create figure for plotting
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
xs = []
ys = []
# Initialize communication with TMP102
tmp102.init()
# Sample temperature every second for 10 seconds
for t in range(0, 10):
# Read temperature (Celsius) from TMP102
temp_c = round(tmp102.read_temp(), 2)
# Add x and y to lists
xs.append(dt.datetime.now().strftime('%H:%M:%S.%f'))
ys.append(temp_c)
# Wait 1 second before sampling temperature again
time.sleep(1)
# Draw plot
ax.plot(xs, ys)
# Format plot
plt.xticks(rotation=45, ha='right')
plt.subplots_adjust(bottom=0.30)
plt.title('TMP102 Temperature over Time')
plt.ylabel('Temperature (deg C)')
# Draw the graph
plt.show()
Save (give it a name like tempgraph.py) and exit. Run the program with:
language:shell
python tempgraph.py
Wait while the program collects data (about 10 seconds). Feel free to breathe on or fan near the temperature sensor to change the ambient temperature (gives you some interesting data to look at). Once the collection has finished, you should be presented with a plot showing how the temperature changed over time.
Code to Note
We create the graph in a very similar manner to the Formatting example in Introduction to Matplotlib. The only difference is that we build the xs and ys lists programmatically. Each second, the temperature is read from the TMP102 sensor and appended to the ys list. The local time (of the Raspberry Pi) is captured with dt.datetime.now() and appended to the xs list.
The xs and ys lists are then used to create a plot with ax.plot(xs, ys). Note that we are explicitly creating a figure and a single set of axes (instead of calling the plt.plot() shortcut). We will use the handle to the axes in the next sections when we look at animating the plot.
Update a Graph in Real Time
Waiting to collect measurements from a sensor before plotting it might work in some situations. Many times, you would like to be able to monitor the output of a sensor in real time, which means you can look for trends as they happen. To accomplish that, we will create an animation where a temperature sample is taken and the graph is updated immediately.
Animation Code
Open a new file (once again, make sure it’s in the same directory as tmp102.py so that we can use the tmp102 module). Copy in the following code:
language:python
import datetime as dt
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import tmp102
# Create figure for plotting
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
xs = []
ys = []
# Initialize communication with TMP102
tmp102.init()
# This function is called periodically from FuncAnimation
def animate(i, xs, ys):
# Read temperature (Celsius) from TMP102
temp_c = round(tmp102.read_temp(), 2)
# Add x and y to lists
xs.append(dt.datetime.now().strftime('%H:%M:%S.%f'))
ys.append(temp_c)
# Limit x and y lists to 20 items
xs = xs[-20:]
ys = ys[-20:]
# Draw x and y lists
ax.clear()
ax.plot(xs, ys)
# Format plot
plt.xticks(rotation=45, ha='right')
plt.subplots_adjust(bottom=0.30)
plt.title('TMP102 Temperature over Time')
plt.ylabel('Temperature (deg C)')
# Set up plot to call animate() function periodically
ani = animation.FuncAnimation(fig, animate, fargs=(xs, ys), interval=1000)
plt.show()
Save and run the code. You should immediately see a graph that gets updated about once every second. Feel free to breathe on the sensor to see how the temperature fluctuates.
Code to Note
To create a real-time plot, we need to use the animation module in matplotlib. We set up the figure and axes in the usual way, but we draw directly to the axes, ax, when we want to create a new frame in the animation.
At the bottom of the code, you’ll see the secret sauce to the animation:
ani = animation.FuncAnimation(fig, animate, fargs=(xs, ys), interval=1000)
FuncAnimation is a special function within the animation module that lets us automate updating the graph. We pass the FuncAnimation() a handle to the figure we want to draw, fig, as well as the name of a function that should be called at regular intervals. We called this function animate() and is defined just above our FuncAnimation() call.
Still in the FuncAnimation() parameters, we set fargs, which are the arguments we want to pass to our animate function (since we are not calling animate() directly from within our own code). Then, we set interval, which is how long we should wait between calls to animate() (in milliseconds).
Note: As an argument to FuncAnimation, notice that animate does not have any parentheses. This is passing a reference to the function and not the result of that function. If you accidentally add parentheses to animate here, animate will be called immediately (only once), and you’ll likely get an error (probably something about a tuple not being callable)!
If you look at the call to animate(), you’ll see that it has 3 parameters that we’ve defined:
def animate(i, xs, ys):
i is the frame number. This parameter is necessary when defining a function for FuncAnimation. Whenever animate() is called, i will be automatically incremented by 1. xs and ys are our lists containing a timestamp and temperature values, respectively. We told FuncAnimation() that we wanted to pass in xs and ys with the fargs parameter. Without explicitly saying we want xs and ys as parameters, we would need to use global variables for remembering the values in xs and ys.
Within animate(), we collect the temperature data and append a timestamp, just like in the previous example. We also truncate both xs and ys to keep them limited to 20 elements each. If we let the lists grow indefinitely, the timestamps would be hard to read, and we would eventually run our of memory.
In order to draw the plot, we must clear the axes with ax.clear() and then plot the line with ax.plot(). If we didn’t clear them each time, plots would just be drawn on top of each other, and the whole graph would be a mess. Similarly, we need to reformat the plot for each frame.
You might notice that the plot updates only once per second (as defined by interval=1000). For some sensors, such as a temperature sensor, this is plenty fast. In fact, you may only want to sample temperature once per minute, hour, or even day. However, this sampling rate might be entirely too low for other sensors, such as distance sensors or accelerometers, where your application requires updates every few milliseconds.
Try lowering the interval to something less than 500. As it turns out, clearing and redrawing the graph is quite an intensive process for our little Pi, and you likely won’t get much better than 2 or 3 updates per second. In the next section, we’re going to show a technique for speeding up the drawing rate, but it means cutting some corners, such as having to set a static range and not showing timestamps.
Speeding Up the Plot Animation
Clearing a graph and redrawing everything can be a time-consuming process (at least in terms of computer time). As a result, our Raspberry Pi can struggle keeping up with more animations when we push it past about 2-3 frames per second (fps). To remedy that, we are going to use a trick known as blitting.
Blitting is an old computer graphics technique where several graphical bitmaps are combined into one. This way, only one needed to be updated at a time, saving the computer from having to redraw the whole scene every time.
Matplotlib allows us to enable blitting in FuncAnimation, but it means we need to re-write how some of the animate() function works. To reap the true benefits of blitting, we need to set a static background, which means the axes can’t scale and we can’t show moving timestamps anymore.
Animation with Blitting Code
Open a new file in the same directory as our tmp102.py module, and copy in the following code:
language:python
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import tmp102
# Parameters
x_len = 200 # Number of points to display
y_range = [10, 40] # Range of possible Y values to display
# Create figure for plotting
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
xs = list(range(0, 200))
ys = [0] * x_len
ax.set_ylim(y_range)
# Initialize communication with TMP102
tmp102.init()
# Create a blank line. We will update the line in animate
line, = ax.plot(xs, ys)
# Add labels
plt.title('TMP102 Temperature over Time')
plt.xlabel('Samples')
plt.ylabel('Temperature (deg C)')
# This function is called periodically from FuncAnimation
def animate(i, ys):
# Read temperature (Celsius) from TMP102
temp_c = round(tmp102.read_temp(), 2)
# Add y to list
ys.append(temp_c)
# Limit y list to set number of items
ys = ys[-x_len:]
# Update line with new Y values
line.set_ydata(ys)
return line,
# Set up plot to call animate() function periodically
ani = animation.FuncAnimation(fig,
animate,
fargs=(ys,),
interval=50,
blit=True)
plt.show()
Save and run the code. A graph should appear with a line that animates much faster than in the previous example (i.e. around 20 fps). You should also note that there are no timestamps (i.e. the x axis does not contain any useful data), and the y axis (temperature) does not automatically scale. In fact, if you were to measure a temperature below 10° or above 40° C, it would not be drawn on the graph.
Code to Note
First, notice that we removed any reference to datetime or timestamps, as they won’t help us with fast plotting here. Feel free to add them back in if you would like to enable some type of logging, but remember that it will slow down the animation.
Next, we set up a number of static parameters. x_len is the number of elements we want to use to create the plot. In this case, we remove elements from the beginning of the list when the plot gets to be more than 200 elements. We also set up a static y_range, which is the minimum and maximum temperature that can be displayed on the graph. To keep things fast, we don’t want to redraw the y axis every frame!
In the animate() function, we only deal with the list of y (temperature) elements, as we know that the x axis doesn’t change. Additionally, instead of redrawing the axes ax as in the previous example, we only update the line object, which we got a handle to earlier in the code:
line, = ax.plot(xs, ys)
The trailing comma on line, allows us to “unpack” the single-element tuple returned by the ax.plot() function. ax.plot() returns a tuple of Line2D objects (in this case, there should be only one Line2D object). As a result, we want a handle to the first object, so we use the trailing comma to say that we want the first object in the tuple and not the whole list itself. See here for more about trailing commas in Python.
After updating the Line2D object with line.set_ydata(ys), we package it into another single-element tuple with return line,, as FuncAnimation() expects our animation function to return a tuple of Line2D objects.
With these changes, we can set the blit parameter to True in our call to FuncAnimation(). This changes the way FuncAnimation() works on the back end to only update the line while leaving the background (everything else) unchanged.
Resources and Going Further
Plotting sensor data can be incredibly useful if you need to make a dashboard to watch the temperature in your server room or if you want to monitor the humidity around your classroom for a science experiment. If you would like to learn more about matplotlib, here are some great resources:
If you are interested in keeping the tmp102.py file in a different directory and still access its functions, we recommend turning it into a package. Here is a good tutorial that shows you how to make your own Python package.
Looking for even more inspiration? Check out these other Raspberry Pi projects:
How much impact can the human body handle? This tutorial will teach you how to build your very own impact force monitor using a helmet, Raspberry Pi Zero, and accelerometer!
In this tutorial, we'll show you how to use the Flask framework for Python to send data from ESP8266 WiFi nodes to a Raspberry Pi over an internal WiFi network.
In this tutorial we will use a Pi AVR Programmer HAT and a Raspberry Pi 3B+ to program an ATMega328P target IC (RedBoard). We will program the Arduino bootloader over SPI using the capsense pad to engage, and then do some programming using avrdude in the command line. We will also cover how the Pi AVR Programmer HAT hardware works in conjunction with Python, avrdude, and shell command files.
The Pi AVR Programmer HAT makes it easy to program AVRs directly from the SPI hardware pins on any Raspberry Pi. It was originally designed as an in-house solution for SparkFun production, but now is offered as a robust programming tool for anyone to purchase. It is by far one of the fastest, most reliable, and hack-able (fully open sourced) AVR programming solutions available. It can be used directly from the command line using avrdude commands, or with some simple setup steps, it can function as a stand-alone programmer with capsense-pad engage and status leds!
All of the design files, firmware, and example programming files can be found here:
There are many reasons for programming your AVR via an in-system programmer (ISP):
If your AVR doesn’t have a bootloader on it, it’s probably the only way to load code.
ISP provides a faster and more reliable code upload.
If your project requires that your hardware UART pins (RX/TX) be connected to another device, this can conflict with serial uploading of code. ISP programming does not require using these pins. Instead, it uses MOSI/MISO/SCK/RESET – Arduino pins D11/D12/D13/RESET. This means you can leave RX and TX connected during development and re-program your AVR with new code without un-plugging and re-plugging any of your programming lines.
With ISP, you can overwrite the bootloader and squeeze out some extra flash space.
ISP allows you to poke at the fuse bits to change many settings, including the brown-out voltage.
Covered In This Tutorial
In this tutorial we will introduce you to all of the important aspects of the Pi AVR Programmer HAT. It’s split into a series of sections, which cover:
Board Overview– A look at the hardware components that make up the Pi AVR Programmer HAT.
Raspberry Pi Setup– To get up and programming, there is a little bit of setup work to do on your Raspberry Pi. Here, we will show what settings to change and what files need to be modified and/or copied.
Hardware Hookup– How to hookup the Pi AVR Programmer from your Raspi to the example Target AVR.
ISP Programming: Stand-Alone– How to use the HAT in stand-alone mode and program an arduino bootloader (or any hex file) via SPI onto an ATMega328p target IC (the Redboard).
ISP Programming: Within the Arduino IDE– How to use the HAT from within the Arduino IDE. A nice one-click solution for programming your Arduino with the speed of ISP.
Speed Test– We take the HAT for a speed test and see just how fast it goes!
Required Materials
Note The wishlist is our list of suggested parts to complete this tutorial. You can use any other Raspberry Pi Model you'd like (0, 2, 3, etc.). In this tutorial we use the 3B+ model. Another good option is the Raspberry Pi 3 Starter Kit, which includes most everything you need to get up and running with your Pi. You can choose a different target Arduino board. We use the Redboard in this tutorial.
To complete this tutorial, you will need the following hardware. You may not need everything though depending on what you have. Add it to your cart, read through the guide, and adjust the cart as necessary.
Suggested Reading
To read even more about the history behind this project and design choices, check out the full tutorial/write up here:
This tutorial will show you how to use a headless Raspberry Pi to flash hex files onto AVR microcontrollers as a stand-alone programmer. It also tells the story about production programming challenges, how SparkFun came to this solution, and all the lessons learned along the way.
Whether you’re a beginner or experienced electronics enthusiast, the Pi AVR Programmer HAT should be easy to get up-and-running. If you’ve programmed an Arduino before, you’ll be well-prepared for the next step. Here are some addtional tutorials we’d recommend reading before continuing on with this one.
This tutorial will teach you what a bootloader is and why you would need to install or reinstall it. We will also go over the process of burning a bootloader by flashing a hex file to an Arduino microcontroller.
Before we get into using our Pi AVR Programmer HAT, let’s quickly overview what components fill the board out:
2x20 Raspi Header– This female header will connect down to the GPIO pins on your Raspberry Pi. When pushed all the way down into the GPIO pins, the HAT will sit flush with the top of your enclosure.
Target VCC Select– Jumpers to choose what logic level you’d like to talk to your target. Options include “TARGET”, 3.3V, and 5V.
Note: The “TARGET” option means that the target IC will provide the logic level reference voltage. “TARGET” is the default setting for this jumper. Also note that the 3.3V and 5V options are provided from those voltages on the Raspi 2x20 headers. Please only close one of these jumpers at a time. Closing more than one jumper will damage your Raspi.
Isolation Switch IC– The multiplexer IC that disconnects the SPI lines from your Pi to the Target.
SPI Interface (1x6 header)– These headers provide optional direct access to the SPI lines. This is in a 1x6 straight header format, so it will mate nicely with your 6-pin Jumper wire. Most programmers use ribbon cables, which will fail after so many insertions. This 1x6 cable and adapter solution is much more robust. You can also opt to remove the supplied jumper cable, and wire up to your target with your own jumper wires or custom adapter.
SPI Interface Adapter (2x3 header)– This adapter mates easily with the standard ISP header found on most AVR boards.
Shutdown Button– Hold this down for 6 seconds to shut down your Pi.
Note: This is simply a momentary button on a GPIO pin. It could be used for other things, but the Python module running the Pi AVR Programmer HAT (test.py) is watching this line to engage a full shutdown of the Pi. After you engage a shut down, please watch the “active” LED on the Raspberry Pi. It is located inside the enclosure next to the power LED. Wait for it to stop blinking, then you can remove power.
Capsense Pad– Use this to engage programming. Simply tap with your finger and that will trigger the onboard capsense ATQ421010 IC which is connected to a GPIO on the Pi.
Status LEDS– Used for indicating power, success and/or fail for various stages of programming.
Label Boxes– These blank white silk-screen boxes are handy for labeling your Pi with a project name and version of your loaded hex file.
AVR ISP Pinouts
AVRs are programmed through an SPI interface. There are six unique signals required for communication between ISP and AVR:
VCC
GND
Reset
MOSI
MISO
SCK
To route those signals between devices, there are two standardized connectors – one 10-pin, 2x5 connector and another 6-pin, 2x3 connector:
AVR ISP Pinouts – Top View.
The Pi AVR Programmer HAT includes a 1x6 jumper cable and adapter which terminates with a 2x3 connector. There are labels on the bottom side near each leg on the SMD header:
ISP Adapter – Top and Bottom View.
If your target device has the 2x5 pinout option, then you could use some of our F/F jumper cables to connect directly from the right-angled 1x6 male header on the HAT PCB to your target.
Before we can do any programming, we need to do some setup work on our Raspberry Pi. If you are fairly familiar with Raspberry Pi, then you should check out the quick setup list for advanced users below. If you are fairly new to Raspberry Pi, then we recommend checking out the following tutorial: The Raspberry Pi 3 Starter Kit Hookup Guide. This offers a great walkthrough to setting up your Raspberry Pi with NOOBS (Raspberry Pi’s easy-to-use graphical OS installer).
Note: You can use any hex file you’d like to program, but for this example, we are going to show how to re-program a RedBoard with an optiboot bootloader hex file.
Put the modified version of rc.local in your /etc folder.
Open permissions on these files (to avoid debug errors). Run the following command: sudo chmod 777 test.py avrdude_gpio.conf pi_program.sh your_firmware.hex.
Adjust pi_program.h to your desired programming settings. Here you can choose fuse bits, device ID (your target type), programming speed (-b 125000 is the flag for setting this).
Launch test.py. Run sudo python test.py (or reboot and rc.local will launch it for you).
Tap PROGRAM!
Closer Look at Repository Files
There are a lot of files in the GitHub repository for this project.
However, you don’t actually need all of them to get up and running. There are quite a few that are extra examples and will show you some more of the advanced features of this programmer. Let’s take a closer look at the most important ones.
test.py
The test.py is the Python module that handles all of the operations of the Pi AVR Programmer HAT. It is launched at bootup from inside rc.local. Once it is up and running, it blinks the “STAT” LED to show that it is alive. It then listens to the capsense pad, finds your firmware hex file, engages programming, parses output from avrdude, and ultimately blinks status LEDs.
It also has some functionality to watch for media drives and pull in any new hex files that live on them. This is an easy way to update your programming hex file on your Pi, but beware, it will overwrite your existing hex file, so be careful. It’s usually a good idea to stop test.py before plugging in any thumbdrives (until you’re absolutely sure you want test.py to pull in the hex file). To stop test.py (and any other background Python modules), simply use this command:
language:bash
sudo killall python
It’s also not a bad idea to back up your hex files elsewhere (outside of /home/pi).
pi_program.sh
The pi_program.sh shell file contains the actual programming calls to avrdude. Test.py launches this shell to engage programming. It is very similar to the types of programming we do in SparkFun production. It allows you to adjust some variables at the top for device and fuse bits. Fuse bits can be a little tricky. If you need to adjust these and want some help, please check out our fuse bits tutorial.
language:bash
# DEVICE
# Here are some commonly used "part no"s in avrdude at SFE
# UN-comment the one you wish to use, or plug in something different
# Use "avrdude -p?" for a list of supported devices
DEVICE=atmega328p
#DEVICE=m32u4
#DEVICE=t2313
#FUSE BITS
HIGH_FUSE=0xD8
LOW_FUSE=0xFF
EXT_FUSE=0xFD # due to masking, 0x05 = 0xFD, 0x07 = 0xFF, ***Note on an ATMEGA2560, ext fuse writes and verifies as 0xFD
LOCK=0x0F # due to masking, 0x0F = 0xCF
Note that it also has a nice little trick at the top to search for a hex file within /home/pi and use that for programming.
language:bash
# get firmware file name
firmware=$(find /home/pi/*.hex)
$firmware .= "/home/pi/$firmware"
Beware, that if you have two hex files in this directory, it will use the first one it finds. You can modify pi_program.sh to call a specific hex file by changing the $firmware variable.
The last thing to note about this file is the manual GPIO control in the middle of calls. In between fuse bits programming and flashing of the hex, there is a manual flip of the GPIO controlling the reset line. After using this programmer for thousands of programming cycles, we found that in order to truly reset the target in between calls to avrdude, a more reliable approach is to quickly toggle that GPIO from the shell file. Without this, the second call to avrdude can occasionally fail to reset the target. But with this toggle, we’ve had 100% success!
The provided avrdude_gpio.conf configuration file is very similar to the default configuration file. The only modification necessary is adding in the linuxspi programmer definition (Note that this also defines your RESET pin). On the Pi AVR Programmer HAT, we have this hard wired to GPIO PIN 26. If you are working from a different .conf file, then this little block of text below needs to be added to the very bottom of the avrdude.conf file.
language:bash
programmer
id = "linuxspi";
desc = "Use the Linux SPI device in /dev/spidev*";
type = "linuxspi";
reset = 26;
;
rc.local
The provided rc.local file is very similar to the default rc.local file you get with most raspberry Pis. This file runs at bootup, so it is handy if you’d like to have some commands run every time you power up your Pi (very useful if you plan to run it headless). The only addtional command added to the default rc.local is as follows. It simply calls Python to launch our test.py module.
language:bash
python /home/pi/test.py &
Note the “&” is very important here. It allows your Pi to continue on and run the GUI. If it wasn’t there, then your Pi would just wake up and run the Python module, and then do nothing else, this is dangerous, because you wouldn’t be able to modify your Pi from here on out. It’s kind of like “bricking” your Pi, so please don’t forget that very important “&”.
Hardware Hookup
Once you have completed the software setup to your Raspi, then it’s time to plug in some hardware!
If you haven’t already, put your Pi into an enclosure. Note that this is optional; it will work just fine without.
Plug in the programming cable into your target IC. In this example, we are plugging into the 2x3 SPI header on the RedBoard. Note that this is polarized. The small white line indicates “pin 1”. Make sure to align those up!
Plug in a USB cable from the Pi to the RedBoard. This will provide power to the RedBoard. You could also opt to power the board via the barrel jack if you wish.
ISP Programming: Stand-Alone (aka Headless)
In this section, we are going to program using the Raspberry Pi “headless” (aka no monitor, mouse or keyboard). We will use the built in capacitive touch pad to engage programming and status LEDs to indicate success or failure. For this example, we will program the Arduino optiboot bootloader onto the RedBoard.
Your hex file– This should live in the ‘/home/pi’ directory.
pi_program.sh– This shell file contains all the settings for programming and can be edited with any text editor.
Note: If you followed along with the setup instructions above, then you should already have the correct hex file and settings. And if you followed along with the hardware hookup above, then you should be set to program your RedBoard.
If you are plugging into the 2x3 header on a different AVR target IC, then we do recommend double checking that you have the polarity correct. The _pin 1_ is usually marked with a line of silk (as on the adapter), but it can also be indicated by a different style pad (usually square vs round).
After your setup and powered, programming is a simple as 1-2-3:
To engage programming in stand-alone use, simply tap the capacitive touch pad labeled, “TAP TO PROGRAM”.
The STAT LED will go solid during programming. This can take a couple seconds – depending on the size of your hex file.
Finally, the SUCCESS and FAIL LEDs will indicated the status at each stage of programming
Note: Because of the way we have set this up to only program the bootloader, the “Serial Upload” LEDs will not light up. This is okay for now. If you’d like to do some serial uploading in stand alone mode, you can try out the serial upload example from the repo here.
To see more about the results of your programming (debug messages from avrdude), you will need to access some files on your raspi. During each cycle of programming, the output from avrdude is saved to some text files that live within /home/pi.
/home/pi/fuse_results.txt
/home/pi/flash_results.txt
/home/pi/SERIAL_UPLOAD/serial_upload_results.txt– Note that this will only be updated if you did any serial uploading
ISP Programming: Command Line
In this section, we are going to program the Target AVR IC (Redboard) using calls to avrdude in the command line. This will require a monitor, keyboard and mouse hooked up to your Pi
Download the bootloader hex file for the RedBoard here:
Save it in the correct directory (i.e. /home/pi/):
Note: Make sure this is the only hex file in that directory, because pi_program.sh is going to find it and use if for programming.
Sanity Check – Device Signature Verification
Before we get into any actual programming, it’s a good idea to ensure our connections are all correct and avrdude is working properly. To do this, we are going to simply call avrdude and ping for the device ID.
The Pi AVR Programmer HAT has an isolation switch between the raspi GPIO pins and the target. This serves as both protection of your rapsi and as a means to “free up” your target once you’re done programming. You can use some handy Python modules to quickly set this switch: enable_switch.py and disable_switch.py. For easy access, it’s a good idea to save them in the /home/pi directory.
Make sure to enable the programming switch by running a quick python module named enable_switch.py.
language:bash
sudo python enable_switch.py
Then run your avrdude command. Here is our first call to avrdude to check the device ID.
This basic command defines the programmer type you’re using and the AVR it’s talking to. AVRDUDE will attempt to read the Device Signature from your AVR, which is different for each AVR type out there. Every ATmega328P should have a device signature of 0x1E950F.
Flash Programming
Now that you’ve verified that everything is in working order, you can do all sorts of memory reading and writing with AVRDUDE. The main piece of memory you probably want to write is flash – the non-volatile memory where the programs are stored.
This command will perform a basic write to flash (using this HEX file as an example):
Writing to flash can sometimes take a little longer than a device ID ping. With the built-in ISP hardware pins and the Pi AVR Programmer HAT, you can use up to 2MHz reliably. For blink.hex, it only takes 0.09 seconds! You’ll see a text status bar scroll by as the device is read, written to, and verified.
Click the image for a closer look.
The -U option command handles all of the memory reads and writes. We tell it we want to work with flash memory, do a write with w, and then tell it the location of the hex file we want to write.
Useful Options
Here are just a few last AVRDUDE tips and tricks before we turn you loose on the AVR world.
Two options required for using AVRDUDE are the programmer type and AVR device specification. The programmer definition, assuming you’re using the Pi AVR Programmer HAT, will be -c linuxspi. Note that you will also need to specify the port (because there are two SPI ports on the raspi. This is done with the -P /dev/spidev0.0 portion of the call. If you need to use a different programmer check out this page and CTRL + F to “-c programmer-id”.
The AVR device type is defined with the -p option. We’ve shown a few examples with the ATmega328P, but what if you’re using an ATtiny85? In that case, you’ll want to put -p t85 instead. Check out the top of this page for an exhaustive list of compatible AVR device types.
Adding one or more -v’s to your AVRDUDE command will enable various levels of verbosity to the action. This is handy if you need a summary of your configuration options, or an in-depth view into what data is being sent to your AVR.
There’s plenty more where that came from. Check out the AVRDUDE Option Documentation for the entire list of commands.
Here, we are going to show how to use the Pi AVR Programmer HAT directly from the Arduino IDE. Note that this functionality is not completely fleshed out. We are currently developing support for this feature and working out any bugs right now. Read all about it on the GitHub pull request here:
It is very important that you have the correct version of Arduino. The version of avrdude that comes with Arduino on your Raspi is probably not capable of using the Pi AVR Programmer HAT. You need Arduino Nightly Version because it comes with the latest version of avrdude – version 6.3-20171130. For this tutorial, we are using Arduino 1.8.6 Hourly Build 2018/07/19.
You need to install the SparkFun Arduino Boards. If you are comfortable using the boards manager, then you can install them directly there. For more help, check out the instructions in the GitHub repository's README.md, and this write up for Installing Custom Boards into Arduino.
Modify the programmers.txt, avrdude.conf and platform.txt files (that is, unless it’s already pulled into the repo by now). You can view it on GitHub at the open pull request for SparkFun Arduino Boards here. For reference, the following screen-shot also shows the differences:
Select “Pi_AVR_Programmer_HAT” from the drop down menu in the Arduino IDE. If you don’t see it in your list of options, then try closing and re-opening Arduino.
Now, instead of using the standard upload button, you can select “Upload Using Programmer”. Ctrl + Shift + U is also pretty handy.
Turn on verbose output to see what’s going on better.
Note: You can only use SparkFun boards for this to work right now (SparkFun Redboard, SparkFun Makey Makey, etc.). If it does get accepted into the arduino avr boards package, then it will work with all other AVRs.
Speed Test - How Fast Can This Thing Go?
For some fun, and to demonstrate the difference in programming speeds, we created a massive Arduino sketch, and recorded some of the programming speeds. Once compiled, this sketch used up a whopping 20440 bytes of memory – this is about 63% of the ATmega328P’s memory. For comparison, blink is only 930 bytes – or 2% of memory.
This test was done with a 5V Arduino running on a 16MHz Crystal (the SparkFun RedBoard), and so that allowed us to program reliably at 2MHz. If you are using a 3.3V Arduino at 8MHz, it would be smart to bring that down to 1MHz. Our experience in production programming at SparkFun has shown us that in fact 1/8 of oscillator speed is the best way to go for nearly 100% reliability.
Here were the speed test results:
Serial upload via Arduino IDE:
6.32s upload
6.89s verify
13.21s total
ISP program via Arduino IDE and Pi AVR Programmer HAT:
4.04s write
3.40s read
7.44s total
ISP program via Pi AVR Programmer HAT in stand alone mode – using test.py and pi_program.sh:
1.57s write
0.74s read
2.31s total
As you can see from the results above, the Pi AVR Programmer HAT used directly from avrdude was much faster than the other options! Almost 11 seconds faster than the traditional serial upload. Dang!
Resources and Going Further
Now that you’ve successfully got your Pi AVR Programmer Hat up and running, it’s time to incorporate it into your own project!
For more information about the SparkFun Pi AVR Programmer Hat, check out the resources below:
Want to check out some other SparkFun production processes or information about burning bootloaders on microcontrollers? Check out some of these related tutorials:
This tutorial will teach you what a bootloader is and why you would need to install or reinstall it. We will also go over the process of burning a bootloader by flashing a hex file to an Arduino microcontroller.
Does your project require high-precision, cutting-edge distance, speed, motion, and/or gesture sensing? We’re not talking ultrasonic, or even infrared here, but 60GHz radar! Say hello to our tiny, pulsed radar friend!
The A111 is a single-chip solution for pulsed coherent radar (PCR) – it comes complete with antennae and an SPI interface capable of speeds of up to 50MHz. Applications for PCR include distance-sensing, and gesture, motion, and speed detection. The sensor can monitor one-or-more objects at distances of up to two meters.
Our breakout board for the A111 includes a 1.8V regulator, voltage-level translation, and it breaks out all pins of the pulsed radar sensor to both 0.1-inch and Raspberry Pi-friendly headers.
Required Materials
To use the A111 you’ll need either an ARMv7 or an ARM Cortex-M4 – the closed-source SDK currently only supports these architectures. This tutorial will explain how to use the radar sensor with a Raspberry Pi– a platform based on an architecture supported by the A111’s SDK.
The A111 Breakout includes a 20-pin, 2x10 female header, which should mate to Raspberry Pi’s of any generation. If you’d rather manually wire the A111 to your Raspberry Pi, male headers and about 9 male-to-female wires should do the trick.
Raspbian:: This tutorial assumes you've already set up a Raspberry Pi with Raspbian. For help installing the Debian-based OS on your Pi, check out the docs on Raspberrypi.org. Or -- better yet! -- check out our Headless Raspberry Pi Setup tutorial.
The A111 Pulsed Radar Breakout is designed to sit directly on top of a Raspberry Pi. It doesn’t span all 40 (2x20) pins of a Raspberry Pi B+ (or later), but the 26-pin – 2x13 – header should be compatible with any Pi.
Solder the 2x13 header so the female side is facing away from the greenish-black A111 IC. Then connect the shield to a Raspberry Pi ensuring that the “Pi Display” text on the breakout matches up with the display header on your Pi. The sensor should be facing up after plugging it in.
A111 Breakout plugged into a Raspberry Pi.
Or, if you’d like to manually wire the breakout up to a Pi, here is the pin-out we’ll use through the rest of this tutorial:
Breakout Pin
Raspberry Pi Pin Name
RasPi Pin Number
CS
SPI0 CS0
24
SCLK
SPI0 SCLK
23
MISO
SPI0 MISO
21
MOSI
SPI0 MOSI
19
INT
GPIO25
22
EN
GPIO27
13
VCCIO
3.3V
1,17
GND
GND
6, 14, 20, etc.
VIN
5V
2, 4
This board breaks out both "VIN" and "VCCIO" pins. "VIN" should power the sensor, which can consume up to about 80mA. "VCCIO" sets the I/O voltage, which may be lower than VIN.
The Raspberry Pi pin breakouts, for example, connect VIN to 5V and VCCIO to 3.3V, as the Raspberry Pi's 3.3V bus may not be able to fully power the A111, but the Pi can only handle 3.3V I/O.
Get the SDK
The software development kit (SDK) for the A111 is, unfortunately, locked behind a closed source blob that currently only supports Cortex-M4 and ARMv7 platforms.
To download the SDK, visit Acconeer’s “Products” page. Towards the bottom, under the “A1 Software Development Kit” header is a link to GET SOFTWARE**. Read through the license, and agree, then request the A1 SDK for Linux ARMv7 software.
Requesting the ARMv7 SDK from Acconeer.
After supplying your email address, you should receive a download link email nearly instantaneously.
SCP the SDK to your Pi
Once downloaded, you’ll probably need to transfer the ZIP'ed SDK over to your Pi. To achieve this, we recommend SCP. If you’re on Windows, WinSCP works very well for transferring files from one device to another.
Using WinSCP to drag-and-drop the SDK into the home directory of your Pi.
If you’re on a Mac or Linux machine, with SCP available, you can use a command like the below to copy the ZIP file over:
(Make sure you replace the acconeer ZIP file name with that of your downloaded SDK version.)
Then cd to the “a111/evk_service/…” directory to prepare to build the example software.
SDK Overview
The A111 SDK includes source code, archived libraries, include files, and documentation for using the A111 pulsed radar sensor. Here’s a quick overview of what’s included with the SDK:
doc– Doxygen-generated documentation for the A111 API and source code.
include– Header and API files which describe how to interact with the pre-compiled A111 libraries.
lib– Pre-compiled A111 static archives. API for these files are provided in the “include” directory.
out– Compiled board and example object and executable files.
rule– Recursive Makefile rules for board and example files.
source– C source files for custom boards and example applications.
makefile– Top-level makefile. Recursively calls files in the “rule” directory to build example and board files.
Adding Custom Example and Board Files
The SparkFun A111 Breakout’s default pins will not work with those of the SDK’s examples. To build and run an example with this board, we have an example board definition, make scripts, and example applications. Click the button below to download these files:
If you copy this ZIP file to your home directory, this command will unzip it to the right directory (assuming your SDK was unzipped to: “~/a111/evk_service_linux_armv7l_xc111_r4a_xr111-3_r1c_a111_r2c”
Once downloaded, these files should be extracted to the like spot in the original ZIP file.
Example of SparkX example files added to the “rule” directory. (Don’t forget the “source” directory files too!)
Build and Run the Test Sketch
Building the Board and Example Applications
Once uploaded to your Pi, executing the make file – and it’s recursive dependencies – should build all of the examples you may use with the A111. To build all board and example files, navigate to the SDK’s top-level directory and type make.
Troubleshooting
If you have any trouble building the board and example files, ensure that you have gcc and make packages installed. (E.g. apt-get install make gcc)
Running the Example Applications
Once compiled you can run the example application (from the top-level SDK folder) by typing:
./out/sparkx_detector_distance
This will run our modified distance-detector example application. This application will begin by calculating raw peak-distances, with a maximum of ten reflections. Following about 125 samples, the example will briefly estimate minimum and maximum thresholds, then continuously monitor for nearby objects until CTRL+C is pressed.
Evaluating the Example Application
The output of the example application can be a little difficult to parse. Here’s an example output, and an explanation of what each of those values printed out means:
Distance detector: Reflections: 5. Seq. nr: 424. (200-700 mm): 648 mm (137), 658 mm (250), 665 mm (255), 676 mm (209), 686 mm (65)
Reflections: <r>– r is the number of reflections – a value between 0 and 10 – visible to the A111 sensor.
Seq. nr <n>– This number – n– should increment with each successive measurement.
(200-700m)– This is the set measurement range of the A111 sensor.
<d0> mm (<a0>), <d1> mm (<a1>), …– dn in this example is the distance of reflection n and an is the amplitude of that reflection.
Running Other Examples
In addition to the distance detector, the SDK includes a few extra examples. To execute these files, run them from the out directory:
sparkx_envelope_service_example– Reads in the envelope data, which us usually further-processed and used in distance, measurement, or other types of algorithms.
sparkx_envelope_iq_example– Advanced version of the envelope service, which includes phase information for very small variations in distance.
sparkx_power_bins_example– Demonstrates how to use A111 “power bins,” which are still, kind of, a mystery to us…
Going Further
For help building bigger, better applications around the A111, refer to Acconeer’s SDK documentation, including:
The AVR-based serial enabled LCD (a.k.a. SerLCD) is a simple and cost effective solution for adding Liquid Crystal Displays (LCDs) into your project. The PCB design on the back of the screen includes an ATMega328P that handles all of the screen control. It can accept commands via serial, I2C and SPI. This simplifies the number of wires needed and allows your project to display all kinds of text and numbers.
The firmware is fully opensource and available for download at the github repo here:
This allows for any customizations you may need. Uploading firmware (custom or updates), is easily done from the Arduino IDE using a Serial Basic. See firmware update instructions in the troubleshooting section of this tutorial for more info.
Also note, the example code used below is all available in the repo (along with many more examples). Before beginning this tutorial, it’s a good idea to clone the repository (or download the entire repo as a zip), to grab all of the examples. But if you prefer, you can always use the “COPY CODE” button on each of the examples below.
We offer three varieties of the AVR-based Serial Enabled LCDs:
Note that these all have identical firmware and can accept the same commands. However, you must adjust your display characters and cursor position as necessary for each model. Also note, there is a jumper on the back of each screen, and this “tells” the firmware how to correctly set the lines and columns for each screen.
Required Materials
To follow along with this tutorial, you will need the following materials at a minimum. Depending on what you have, you may not need everything on this list. Add it to your cart, read through the guide, and adjust the cart as necessary.
The AVR ATMega328p (with Arduino-compatible bootloader) is populated on the back of each LCD screen and handles all of the LCD control
3 communication options: Serial, I2C and SPI
Adjustable I2C address controlled via software special commands (0x72 default)
Emergency reset to factory settings (Jumper RX to GND on bootup)
Operational backspace character
Incoming buffer stores up to 80 characters
Pulse width modulation of backlight allows direct control of backlight brightness and current
consumption
Pulse width modulation of contrast allows for software defined contrast amount. The previous backpack versions of this product required adjusting the contrast via a hardware trim-pot which was less precise and less accessible in most enclosed projects.
User definable splash screen
Open-sourced firmware and Arduino-compatible bootloader enables updates via the Arduino IDE
AVR-Based Serial Controller (3.3V logic only)
Using these screens, it is easy to connect to any microcontroller that has a serial UART, I2C, or SPI. You could also use a single board computer such as the Raspberry Pi if you wanted. Whatever you choose, please make sure you convert to 3.3V Logic! In our examples below, we chose to use our Redboard with a Logic Level Converter.
Example setup including 5V Redboard and Logic Level Converter
Connection Options
Both sizes of these screens (16x2 and 20x4) have a row of headers along the top side. This is where you can connect power and your choice of communication protocol (Serial UART, I2C, or SPI). It also has our 6-pin Arduino Serial port available for convenient firmware updates.
Main header pinouts
Note, if you choose Serial UART, there is a handy 3-pin JST footprint. This includes the minimum connections needed: RX, GND and VIN. Our JST Jumper 3 Wire Assembly is a good way to go:
Location on screen
JST Jumper 3 Wire Assembly
Input Voltage (VDD) and Logic Levels
All of these screens can be powered by 3.3-9V. You have two pin options for connecting up power. One is labeled “RAW” and the other (on the 3-pin JST) is labeled “3.3-9V”. Note, the pin labeled “+” is NOT a power input pin! This is connected to the VCC of the ATMega329p (3.3V).
Power Input pin options and Atmega328 VCC pin
Pro tip: If you plan to run 100% brightness on all three colors (red, green, blue), then it would be best to keep your power input voltage low. As close to 3.3V as possible is best. This will keep the on-board linear 3.3V voltage regulator nice and cool. It can accept up to 9V and power all three backlights at 100%, but it will get a little warm, and over years of use, potentially damage the vreg. For more information about vregs and why they heat up sometimes, please check out our tutorial: Power and Thermal Dissipation
⚡ Warning! ONLY USE 3.3V LOGIC LEVELS when connecting to any of the data pins on the LCD screens. Connecting directly from a 5V microcontroller (such as the Redboard) will damage your LCD screen permanantly!
Contrast Control
The on-board ATMega328p controls the contrast of the screen using a PWM signal. This can be adjusted by sending a command via Serial UART, I2C, or SPI. The screens ship out with the contrast setting set to 10, which we have found works well for most environments. Temperature and supply voltage can affect the contrast of the LCD, so you may need to adjust it accordingly. For more info about contrast and a detailed example, please see the section below called: Serial UART: Example Code - Contrast Control with a Trimpot.
Hardware Hookup - Initial
Install Headers
For this tutorial, we are going to try out Serial UART, I2C, and SPI. In order to easily follow along using a breadboard, solder some headers to the connection ports along the side of your screen. Also solder some headers onto either side of the Logic Level converter. This will allow us to easily plug these into the breadboard and wire each data line up with jumper wires.
Headers on screen
Headers on logic level converter
Note, we are going to use the 16x2 model with RGB backlight during this tutorial. If you are using a different model (RGB text or the 20x4), then the header pin-out and spacing is identical.
Connecting to an Arduino
Warning! Do not use the TX pin from your Arduino (aka "hardware serial TX") to control your LCD.
The TX pin is used for Serial Uploads of new sketches onto your Arduino, and will cause problems for both your Arduino and the LCD. In other words, when you upload new code to your “project” Arduino, it will be confused because the screen is sharing that TX pin. Please only use software serial to control your LCD. For all of the Serial UART examples, we have setup software serial on D7.
Warning! The RX input should be a TTL-level signal of 3.3V. If you are using a 5V Arduino, you will need a logic level converter between the two.
You should NOT connect the board directly to RS232-level voltages (which are around +/-10V). This will damage the board. For more information on RS232, see our explanation here). If you do wish to connect this display to RS232 signals, you can use a level-shifting board to translate the RS232 signals to TTL-level signals.
For the first set of examples in this tutorial (SERIAL UART), there are three connections you need to make to the LCD: RX, GND, and RAW. For the other communication protocols that we will explore later, you will need to wire up some other lines. Please see the following Fritzing graphic to see how to wire up your connections through a logic level converter.
Firmware Overview
The most basic way to use these screens is to simply send characters to them. They will display on the screen as you send them, and then if you go beyond the last character of the screen, it will begin overwriting from the first spot.
You can choose to send characters over Serial UART, I2C, or SPI. The best way to learn about each communication protocol is to try out the “basic” example located in the github repository.
Note: The examples in this tutorial assume you are using the latest version of the Arduino IDE on your desktop. If this is your first time using Arduino, please review our tutorial on installing the Arduino IDE.
In addition to basic operation, there are special commands you can send the screen for special operations (like “clear screen” or set cursor position) and configuration settings (like BAUD RATE).
Configuration & Command Set
The special character for “or” (aka ‘|’) used to tell the screen to enter “settings mode”. You then follow this command with another special character (usually “ctrl+c”, ctrl+d, etc.). A complete table of commands are shown below in ASCII, DEC and HEX. Any one of these representations is acceptable when sending a command character.
The HD44780 LCD controller is very common. The extended commands for this chip include but are not limited to those described in table. Please refer to the HD44780 datasheet for more information.
Note, this “cheat sheet” is also located at the top of each example code section for easy reference.
ASCII
DEC
HEX
Description
'|'
124
0x7C
Enter Settings Mode
ctrl+h
8
0x08
Software reset of the system
ctrl+i
9
0x09
Enable/disable splash screen
ctrl+j
10
0x0A
Save currently displayed text as splash
ctrl+k
11
0x0B
Change baud to 2400bps
ctrl+l
12
0x0C
Change baud to 4800bps
ctrl+m
13
0x0D
Change baud to 9600bps
ctrl+n
14
0x0E
Change baud to 14400bps
ctrl+o
15
0x0F
Change baud to 19200bps
ctrl+p
16
0x10
Change baud to 38400bps
ctrl+q
17
0x11
Change baud to 57600bps
ctrl+r
18
0x12
Change baud to 115200bps
ctrl+s
19
0x13
Change baud to 230400bps
ctrl+t
20
0x14
Change baud to 460800bps
ctrl+u
21
0x15
Change baud to 921600bps
ctrl+v
22
0x16
Change baud to 1000000bps
ctrl+w
23
0x17
Change baud to 1200bps
ctrl+x
24
0x18
Change the contrast. Follow Ctrl+x with number 0 to 255. 40 is default.
ctrl+y
25
0x19
Change the TWI address. Follow Ctrl+x with number 0 to 255. 114 (0x72) is default.
ctrl+z
26
0x1A
Enable/disable ignore RX pin on startup (ignore emergency reset)
'-'
45
0x2D
Clear display. Move cursor to home position.
n/a
128-157
0x80-0x9D
Set the primary backlight brightness. 128 = Off, 157 = 100%.
n/a
158-187
0x9E-0xBB
Set the green backlight brightness. 158 = Off, 187 = 100%.
n/a
188-217
0xBC-0xD9
Set the blue backlight brightness. 188 = Off, 217 = 100%.
OpenLCD “Cheat Sheet” Command Set
Clear Screen and Set Cursor Position
Clear display and set cursor position are the two commands that are used frequently. To clear the screen, send the control character ‘|’ followed by ‘-’. Clearing the screen resets the cursor position back to position 0 (i.e. the first character on the first line).
Here’s how you could do it doing a Serial UART write on software serial:
language:cpp
OpenLCD.write('|'); //Send setting character
OpenLCD.write('-'); //Send clear display character
Note, most of the example sketches in the repo use these two commands during setup(), so you can try any of the examples out to see these commands in action.
To set the active cursor position, send the control character 254 followed by 128 + row + position. To give this a shot, check out the complete example sketch in the github repo or copy and paste the following into your Arduino IDE:
language:cpp
/*
OpenLCD is an LCD with Serial/I2C/SPI interfaces.
By: Nathan Seidle
SparkFun Electronics
Date: April 19th, 2015
License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).
OpenLCD gives the user multiple interfaces (serial, I2C, and SPI) to control an LCD. SerLCD was the original
serial LCD from SparkFun that ran on the PIC 16F88 with only a serial interface and limited feature set.
This is an updated serial LCD.
This example shows how to change the position of the cursor. This is very important as this is the
fastest way to update the screen, ie - rather than clearing the display and re-transmitting a handful of bytes
a cursor move allows us to re-paint only what we need to update.
We assume the module is currently at default 9600bps.
We use software serial because if OpenLCD is attached to an Arduino's hardware serial port during bootloading
it can cause problems for both devices.
Note: If OpenLCD gets into an unknown state or you otherwise can't communicate with it send 18 (0x12 or ctrl+r)
at 9600 baud while the splash screen is active and the unit will reset to 9600 baud.
Emergency reset: If you get OpenLCD stuck into an unknown baud rate, unknown I2C address, etc, there is a
safety mechanism built-in. Tie the RX pin to ground and power up OpenLCD. You should see the splash screen
then "System reset Power cycle me" and the backlight will begin to blink. Now power down OpenLCD and remove
the RX/GND jumper. OpenLCD is now reset to 9600bps with a I2C address of 0x72. Note: This feature can be
disabled if necessary. See *Ignore Emergency Reset* for more information.
To get this code to work, attached an OpenLCD to an Arduino Uno using the following pins:
RX (OpenLCD) to Pin 7 (Arduino)
VIN to 5V
GND to GND
Command cheat sheet:
ASCII / DEC / HEX
'|' / 124 / 0x7C - Put into setting mode
Ctrl+c / 3 / 0x03 - Change width to 20
Ctrl+d / 4 / 0x04 - Change width to 16
Ctrl+e / 5 / 0x05 - Change lines to 4
Ctrl+f / 6 / 0x06 - Change lines to 2
Ctrl+g / 7 / 0x07 - Change lines to 1
Ctrl+h / 8 / 0x08 - Software reset of the system
Ctrl+i / 9 / 0x09 - Enable/disable splash screen
Ctrl+j / 10 / 0x0A - Save currently displayed text as splash
Ctrl+k / 11 / 0x0B - Change baud to 2400bps
Ctrl+l / 12 / 0x0C - Change baud to 4800bps
Ctrl+m / 13 / 0x0D - Change baud to 9600bps
Ctrl+n / 14 / 0x0E - Change baud to 14400bps
Ctrl+o / 15 / 0x0F - Change baud to 19200bps
Ctrl+p / 16 / 0x10 - Change baud to 38400bps
Ctrl+q / 17 / 0x11 - Change baud to 57600bps
Ctrl+r / 18 / 0x12 - Change baud to 115200bps
Ctrl+s / 19 / 0x13 - Change baud to 230400bps
Ctrl+t / 20 / 0x14 - Change baud to 460800bps
Ctrl+u / 21 / 0x15 - Change baud to 921600bps
Ctrl+v / 22 / 0x16 - Change baud to 1000000bps
Ctrl+w / 23 / 0x17 - Change baud to 1200bps
Ctrl+x / 24 / 0x18 - Change the contrast. Follow Ctrl+x with number 0 to 255. 120 is default.
Ctrl+y / 25 / 0x19 - Change the TWI address. Follow Ctrl+x with number 0 to 255. 114 (0x72) is default.
Ctrl+z / 26 / 0x1A - Enable/disable ignore RX pin on startup (ignore emergency reset)
'-' / 45 / 0x2D - Clear display. Move cursor to home position.
/ 128-157 / 0x80-0x9D - Set the primary backlight brightness. 128 = Off, 157 = 100%.
/ 158-187 / 0x9E-0xBB - Set the green backlight brightness. 158 = Off, 187 = 100%.
/ 188-217 / 0xBC-0xD9 - Set the blue backlight brightness. 188 = Off, 217 = 100%.
For example, to change the baud rate to 115200 send 124 followed by 18.
*/
#include <SoftwareSerial.h>
SoftwareSerial OpenLCD(6, 7); //RX (not used), TX
int counter = 250;
void setup()
{
Serial.begin(9600); //Start serial communication at 9600 for debug statements
Serial.println("OpenLCD Example Code");
OpenLCD.begin(115200); //Begin communication with OpenLCD
//Send the reset command to the display - this forces the cursor to return to the beginning of the display
OpenLCD.write('|'); //Send setting character
OpenLCD.write('-'); //Send clear display character
OpenLCD.print("Hello World! Counter: "); //For 16x2 LCDs
//OpenLCD.print("Hello World! Counter: "); //For 20x4 LCDs
}
void loop()
{
OpenLCD.write(254); //Send command character
OpenLCD.write(128 + 64 + 9); //Change the position (128) of the cursor to 2nd row (64), position 9 (9)
OpenLCD.print(counter++); //Re-print the counter
//OpenLCD.print(""); //When the counter wraps back to 0 it leaves artifacts on the display
delay(2); //Hang out for a bit if we are running at 115200bps
}
The two lines of code that are actually changing the cursor position are as follows:
language:cpp
OpenLCD.write(254); //Send command character
OpenLCD.write(128 + 64 + 9); //Change the position (128) of the cursor to 2nd row (64), position 9 (9)
The example sets the cursor to the second row, position 9. To set the cursor to the first row, position 0, the command would look like this:
language:cpp
OpenLCD.write(254); //Send command character
OpenLCD.write(128 + 0 + 0); //Change the position (128) of the cursor to 1st row (0), position 0 (0)
Use the following tables to see row commands and available positions:
16 Character Displays
Line Number (command)
Viewable Cursor Positions
1 (0)
0-15
2 (64)
0-15
20 Character Displays
Line Number (command)
Viewable Cursor Positions
1 (0)
0-19
2 (64)
0-19
3 (20)
0-19
4 (84)
0-19
Setting Up the LCD Size
You should never need to send any screen size configuration commands to setup these screens. During setup(), the firmware looks at a specific pin that is either left floating or tied high. From this it can determine which screen size it is populated on (16x2 or 20x4). However, the commands are still available for manually setting the width and lines.
ASCII
DEC
HEX
Description
'|'
124
0x7C
Enter Settings Mode
ctrl+c
3
0x03
Change width to 20
ctrl+d
4
0x04
Change width to 16
ctrl+e
5
0x05
Change lines to 4
ctrl+f
6
0x06
Change lines to 2
ctrl+g
7
0x07
Change lines to 1
Changing the Baud Rate
The screens ship out with a default baud rate setting to 9600 baud, but they can be set to a variety of baud rates. The 11.0592 MHz crystal provides greater clock accuracy and allows for baud rates up to 1MHz!
To change the baud rate, you will need to send two commands: First, send the “|” command to enter settings mode. Second, send the desired baud rate command. (For example, to set it to 57600, you’d send |, then Ctrl + q)
Caution! Once you change the baud rate, you need to change the baud rate of your controlling device to match this. To change the LCD's baud rate from 9600 to 57600, first enter the control character 0x7C control and 0x11. Then adjust your microcontroller's code to match the baud rate of 57600.
Here is a table will all of the available baud rate settings commands:
ASCII
DEC
HEX
Description
'|'
124
0x7C
Enter Settings Mode
ctrl+k
11
0x0B
Change baud to 2400bps
ctrl+l
12
0x0C
Change baud to 4800bps
ctrl+m
13
0x0D
Change baud to 9600bps
ctrl+n
14
0x0E
Change baud to 14400bps
ctrl+o
15
0x0F
Change baud to 19200bps
ctrl+p
16
0x10
Change baud to 38400bps
ctrl+q
17
0x11
Change baud to 57600bps
ctrl+r
18
0x12
Change baud to 115200bps
ctrl+s
19
0x13
Change baud to 230400bps
ctrl+t
20
0x14
Change baud to 460800bps
ctrl+u
21
0x15
Change baud to 921600bps
ctrl+v
22
0x16
Change baud to 1000000bps
OpenLCD Baud Rate Command Set
If your screen enters into an unknown state or you otherwise can’t communicate with it, try sending a Ctrl + r (0x12) character at 9600 baud while the splash screen is active (during the first 500 ms of boot-up) and the unit will reset to 9600 baud.
Backlight Brightness
These screens provide you with control of the backlight to one of 30 different brightness levels on each of the colors (Red, Green and Blue). With this, you can mix colors together to get almost any custom color you’d like. This is also handy when power consumption of the unit must be minimized. By reducing the brightness, the overall backlight current consumption is reduced.
Note: The LCD screens that have RGB text on black background, the control of the "backlight" is actually controlling the RGB values of the text brightness.
To change the backlight brightness, you will need to send two commands: First, send the “|” command to enter settings mode. Second, send a number that corresponds to the color and brightness you’d like to set. (For example, to set the Green backlight to 100%, you’d send |, then 187). For some good example code on how to adjust any and all of the colors, check out the example sketch here or copy and paste the code below into your Arduino IDE:
language:cpp
/*
OpenLCD is an LCD with serial/I2C/SPI interfaces.
By: Nathan Seidle
SparkFun Electronics
Date: April 19th, 2015
License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).
OpenLCD gives the user multiple interfaces (serial, I2C, and SPI) to control an LCD. SerLCD was the original
serial LCD from SparkFun that ran on the PIC 16F88 with only a serial interface and limited feature set.
This is an updated serial LCD.
This example shows how to change the backlight brightness. We assume the module is currently at default 9600bps.
We use software serial because if OpenLCD is attached to an Arduino's hardware serial port during bootloading
it can cause problems for both devices.
Note: If OpenLCD gets into an unknown state or you otherwise can't communicate with it send 18 (0x12 or ctrl+r)
at 9600 baud while the splash screen is active and the unit will reset to 9600 baud.
Emergency reset: If you get OpenLCD stuck into an unknown baud rate, unknown I2C address, etc, there is a
safety mechanism built-in. Tie the RX pin to ground and power up OpenLCD. You should see the splash screen
then "System reset Power cycle me" and the backlight will begin to blink. Now power down OpenLCD and remove
the RX/GND jumper. OpenLCD is now reset to 9600bps with a I2C address of 0x72. Note: This feature can be
disabled if necessary. See *Ignore Emergency Reset* for more information.
To get this code to work, attached an OpenLCD to an Arduino Uno using the following pins:
RX (OpenLCD) to Pin 7 (Arduino)
VIN to 5V
GND to GND
Command cheat sheet:
ASCII / DEC / HEX
'|' / 124 / 0x7C - Put into setting mode
Ctrl+c / 3 / 0x03 - Change width to 20
Ctrl+d / 4 / 0x04 - Change width to 16
Ctrl+e / 5 / 0x05 - Change lines to 4
Ctrl+f / 6 / 0x06 - Change lines to 2
Ctrl+g / 7 / 0x07 - Change lines to 1
Ctrl+h / 8 / 0x08 - Software reset of the system
Ctrl+i / 9 / 0x09 - Enable/disable splash screen
Ctrl+j / 10 / 0x0A - Save currently displayed text as splash
Ctrl+k / 11 / 0x0B - Change baud to 2400bps
Ctrl+l / 12 / 0x0C - Change baud to 4800bps
Ctrl+m / 13 / 0x0D - Change baud to 9600bps
Ctrl+n / 14 / 0x0E - Change baud to 14400bps
Ctrl+o / 15 / 0x0F - Change baud to 19200bps
Ctrl+p / 16 / 0x10 - Change baud to 38400bps
Ctrl+q / 17 / 0x11 - Change baud to 57600bps
Ctrl+r / 18 / 0x12 - Change baud to 115200bps
Ctrl+s / 19 / 0x13 - Change baud to 230400bps
Ctrl+t / 20 / 0x14 - Change baud to 460800bps
Ctrl+u / 21 / 0x15 - Change baud to 921600bps
Ctrl+v / 22 / 0x16 - Change baud to 1000000bps
Ctrl+w / 23 / 0x17 - Change baud to 1200bps
Ctrl+x / 24 / 0x18 - Change the contrast. Follow Ctrl+x with number 0 to 255. 120 is default.
Ctrl+y / 25 / 0x19 - Change the TWI address. Follow Ctrl+x with number 0 to 255. 114 (0x72) is default.
Ctrl+z / 26 / 0x1A - Enable/disable ignore RX pin on startup (ignore emergency reset)
'-' / 45 / 0x2D - Clear display. Move cursor to home position.
/ 128-157 / 0x80-0x9D - Set the primary backlight brightness. 128 = Off, 157 = 100%.
/ 158-187 / 0x9E-0xBB - Set the green backlight brightness. 158 = Off, 187 = 100%.
/ 188-217 / 0xBC-0xD9 - Set the blue backlight brightness. 188 = Off, 217 = 100%.
For example, to change the baud rate to 115200 send 124 followed by 18.
*/
#include <SoftwareSerial.h>
SoftwareSerial OpenLCD(6, 7); //RX (not used), TX
byte counter = 0;
void setup()
{
Serial.begin(9600); //Begin local communication for debug statements
OpenLCD.begin(9600); //Begin communication with OpenLCD
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(158 + 0); //Set green backlight amount to 0%
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(188 + 0); //Set blue backlight amount to 0%
}
void loop()
{
//Control red backlight
Serial.println("Mono/Red backlight set to 0%");
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(128); //Set white/red backlight amount to 0%
delay(2000);
//Control red backlight
Serial.println("Mono/Red backlight set to 51%");
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(128 + 15); //Set white/red backlight amount to 51%
delay(2000);
//Control red backlight
Serial.println("Mono/Red backlight set to 100%");
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(128 + 29); //Set white/red backlight amount to 100%
delay(2000);
//The following green and blue backlight control only apply if you have an RGB backlight enabled LCD
all_off(); // turn off all backlights - see function below
//Control green backlight
Serial.println("Green backlight set to 51%");
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(158 + 15); //Set green backlight amount to 51%
delay(2000);
//Control green backlight
Serial.println("Green backlight set to 100%");
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(158 + 29); //Set green backlight amount to 100%
delay(2000);
all_off(); // turn off all backlights - see function below
//Control blue backlight
Serial.println("Blue backlight set to 51%");
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(188 + 15); //Set blue backlight amount to 51%
delay(2000);
//Control blue backlight
Serial.println("Blue backlight set to 100%");
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(188 + 29); //Set blue backlight amount to 100%
delay(2000);
all_off(); // turn off all backlights - see function below
}
void all_off(void)
{
// Set all colors to 0
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(128); //Set white/red backlight amount to 0%
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(158 + 0); //Set green backlight amount to 0%
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(188 + 0); //Set blue backlight amount to 0%
delay(2000);
}
And here is a table showing all of the available backlight settings and commands:
ASCII
DEC
HEX
Description
n/a
128-157
0x80-0x9D
Set the primary backlight brightness. 128 = Off, 157 = 100%.
n/a
158-187
0x9E-0xBB
Set the green backlight brightness. 158 = Off, 187 = 100%.
n/a
188-217
0xBC-0xD9
Set the blue backlight brightness. 188 = Off, 217 = 100%.
OpenLCD Backlight Command Set
Splash Screen
These LCD screens have a splash screen by default that reads “SparkFun OpenLCD Baud:9600”. This splash screen verifies that the unit is powered, working correctly, and that the connection to the LCD is correct. The splash screen is displayed for 500 ms during boot-up and may be turned off if desired.
Setting Splash Screen
To make a custom splash screen, you need to send the characters you want to display (above we sent the characters “Custom Splash Looking good!”) followed by |, then Ctrl + j). You will see a quick pop-up message display “Flash Recorded”. Cycle power to test. You can also try out the example sketch located in the github repo or copy and paste the code below into your Arduino IDE:
language:cpp
/*
OpenLCD is an LCD with serial/I2C/SPI interfaces.
By: Nathan Seidle, Pete Lewis
SparkFun Electronics
Date: 7/26/2018
License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).
OpenLCD gives the user multiple interfaces (serial, I2C, and SPI) to control an LCD. SerLCD was the original
serial LCD from SparkFun that ran on the PIC 16F88 with only a serial interface and limited feature set.
This is an updated serial LCD.
This example shows how to change the Splash Screen contents. We assume the module is currently at default 9600bps.
We use software serial because if OpenLCD is attached to an Arduino's hardware serial port during bootloading
it can cause problems for both devices.
Note: If OpenLCD gets into an unknown state or you otherwise can't communicate with it send 18 (0x12 or ctrl+r)
at 9600 baud while the splash screen is active and the unit will reset to 9600 baud.
Emergency reset: If you get OpenLCD stuck into an unknown baud rate, unknown I2C address, etc, there is a
safety mechanism built-in. Tie the RX pin to ground and power up OpenLCD. You should see the splash screen
then "System reset Power cycle me" and the backlight will begin to blink. Now power down OpenLCD and remove
the RX/GND jumper. OpenLCD is now reset to 9600bps with a I2C address of 0x72. Note: This feature can be
disabled if necessary. See *Ignore Emergency Reset* for more information.
To get this code to work, attached an OpenLCD to an Arduino Uno using the following pins:
RX (OpenLCD) to Pin 7 (Arduino)
VIN to 5V
GND to GND
Command cheat sheet:
ASCII / DEC / HEX
'|' / 124 / 0x7C - Put into setting mode
Ctrl+c / 3 / 0x03 - Change width to 20
Ctrl+d / 4 / 0x04 - Change width to 16
Ctrl+e / 5 / 0x05 - Change lines to 4
Ctrl+f / 6 / 0x06 - Change lines to 2
Ctrl+g / 7 / 0x07 - Change lines to 1
Ctrl+h / 8 / 0x08 - Software reset of the system
Ctrl+i / 9 / 0x09 - Enable/disable splash screen
Ctrl+j / 10 / 0x0A - Save currently displayed text as splash
Ctrl+k / 11 / 0x0B - Change baud to 2400bps
Ctrl+l / 12 / 0x0C - Change baud to 4800bps
Ctrl+m / 13 / 0x0D - Change baud to 9600bps
Ctrl+n / 14 / 0x0E - Change baud to 14400bps
Ctrl+o / 15 / 0x0F - Change baud to 19200bps
Ctrl+p / 16 / 0x10 - Change baud to 38400bps
Ctrl+q / 17 / 0x11 - Change baud to 57600bps
Ctrl+r / 18 / 0x12 - Change baud to 115200bps
Ctrl+s / 19 / 0x13 - Change baud to 230400bps
Ctrl+t / 20 / 0x14 - Change baud to 460800bps
Ctrl+u / 21 / 0x15 - Change baud to 921600bps
Ctrl+v / 22 / 0x16 - Change baud to 1000000bps
Ctrl+w / 23 / 0x17 - Change baud to 1200bps
Ctrl+x / 24 / 0x18 - Change the contrast. Follow Ctrl+x with number 0 to 255. 120 is default.
Ctrl+y / 25 / 0x19 - Change the TWI address. Follow Ctrl+x with number 0 to 255. 114 (0x72) is default.
Ctrl+z / 26 / 0x1A - Enable/disable ignore RX pin on startup (ignore emergency reset)
'-' / 45 / 0x2D - Clear display. Move cursor to home position.
/ 128-157 / 0x80-0x9D - Set the primary backlight brightness. 128 = Off, 157 = 100%.
/ 158-187 / 0x9E-0xBB - Set the green backlight brightness. 158 = Off, 187 = 100%.
/ 188-217 / 0xBC-0xD9 - Set the blue backlight brightness. 188 = Off, 217 = 100%.
For example, to change the baud rate to 115200 send 124 followed by 18.
*/
#include <SoftwareSerial.h>
SoftwareSerial OpenLCD(6, 7); //RX (not used), TX
void setup()
{
Serial.begin(9600); //Begin local communication for debug statements
OpenLCD.begin(9600); //Begin communication with OpenLCD
delay(1000);
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write('-'); // clear screen
delay(1000);
OpenLCD.print("Custom Splash Looking good!"); // Send our new content to display - this will soon become our new splash screen.
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(10); //Set current contents to splash screen memory (this is also a "ctrl-j", if you are doing it manually)
}
void loop()
{
// nothing here, just doing this example in setup()
}
Turning Splash Screen On/Off
To disable the splash screen, send |, then Ctrl + i. Every time this command is sent to the unit, the splash screen display option will toggle. If the splash screen is currently being displayed, sending |, then Ctrl + i will disable the splash screen during the next boot, and sending |, then Ctrl + i characters again will enable the splash screen.
Serial UART: Hardware Hookup
Here’s how to setup your hardware for most of the Serial UART example code.
Note, some other examples require additional wiring (for example the contrast example requires a trimpot for variable control). We will show Fritzing graphics for each example covered in this tutorial. For the remaining tutorials, please look at the comments at the top of the example code for info on how to hookup the hardware.
Serial UART: Example Code - Basic
You can download the latest example code for this experiment from the github repo or you can copy and paste the following code into your Arduino IDE:
language:cpp
/*
OpenLCD is an LCD with serial/I2C/SPI interfaces.
By: Nathan Seidle
SparkFun Electronics
Date: April 19th, 2015
License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).
This example shows how to display a counter on the display over serial. We use software serial because if
OpenLCD is attached to an Arduino's hardware serial port during bootloading it can cause problems for both devices.
To get this code to work, attached an OpenLCD to an Arduino Uno using the following pins:
RX (OpenLCD) to Pin 7 (Arduino)
VIN to 5V
GND to GND
*/
#include <SoftwareSerial.h>
SoftwareSerial OpenLCD(6, 7); //RX, TX
byte counter = 0;
byte contrast = 10;
void setup()
{
Serial.begin(9600); //Start serial communication at 9600 for debug statements
Serial.println("OpenLCD Example Code");
OpenLCD.begin(9600); //Start communication with OpenLCD
//Send contrast setting
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(24); //Send contrast command
OpenLCD.write(contrast);
}
void loop()
{
//Send the clear command to the display - this returns the cursor to the beginning of the display
OpenLCD.write('|'); //Setting character
OpenLCD.write('-'); //Clear display
OpenLCD.print("Hello World! Counter: "); //For 16x2 LCD
//OpenLCD.print("Hello World! Counter: "); //For 20x4 LCD
OpenLCD.print(counter++);
delay(250); //Hang out for a bit
}
Here's what you should see after uploading the code to your Arduino. Try changing the text with a different message!
To send text to the board, wait 1/2 second (500ms) after power up for the splash screen to clear, then send text to the display through your serial port. The display understands all of the standard ASCII characters (upper and lowercase text, numbers, and punctuation), plus a number of graphic symbols and Japanese characters. See the HD44780 datasheet for the full list of supported characters.
If you send data that goes past the end of the first line, it will skip to the start of the second line. If you go past the end of the second line, the display will jump back up to the beginning of the first line.
Tip: You can simulate a scrolling window in software by copying the second line to the first line, and clearing the second line.
Serial UART: Example Code - Contrast Control with a Trimpot
For this contrast control example, you will need to wire up a trimpot. This will allow you to adjust the contrast in real time and find the best setting for your environment. Wire things up like this:
You can download the latest example code for this experiment from the github repo or you can copy and paste the following code into your Arduino IDE:
language:cpp
/*
OpenLCD is an LCD with Serial/I2C/SPI interfaces.
By: Nathan Seidle
SparkFun Electronics
Date: April 19th, 2015
License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).
OpenLCD gives the user multiple interfaces (serial, I2C, and SPI) to control an LCD. SerLCD was the original
serial LCD from SparkFun that ran on the PIC 16F88 with only a serial interface and limited feature set.
This is an updated serial LCD.
This example shows how to change the contrast using a trimpot. We assume the module is currently at
default 9600bps.
We use software serial because if OpenLCD is attached to an Arduino's hardware serial port during bootloading
it can cause problems for both devices.
Note: If OpenLCD gets into an unknown state or you otherwise can't communicate with it send 18 (0x12 or ctrl+r)
at 9600 baud while the splash screen is active and the unit will reset to 9600 baud.
Emergency reset: If you get OpenLCD stuck into an unknown baud rate, unknown I2C address, etc, there is a
safety mechanism built-in. Tie the RX pin to ground and power up OpenLCD. You should see the splash screen
then "System reset Power cycle me" and the backlight will begin to blink. Now power down OpenLCD and remove
the RX/GND jumper. OpenLCD is now reset to 9600bps with a I2C address of 0x72. Note: This feature can be
disabled if necessary. See *Ignore Emergency Reset* for more information.
To get this code to work, attached an OpenLCD to an Arduino Uno using the following pins:
RX (OpenLCD) to Pin 7 (Arduino)
VIN to 5V
GND to GND
Hook a trimpot up:
Pin 1 - 5V
Pin 2 - A0
Pin 3 - GND
*/
#include <SoftwareSerial.h>
SoftwareSerial OpenLCD(6, 7); //RX (not used), TX
byte counter = 0;
void setup()
{
Serial.begin(9600); //Start serial communication at 9600 for debug statements
Serial.println("OpenLCD Example Code");
OpenLCD.begin(9600); //Begin communication with OpenLCD
//Send the reset command to the display - this forces the cursor to return to the beginning of the display
OpenLCD.write('|'); //Send setting character
OpenLCD.write('-'); //Send clear display character
OpenLCD.print("Contrast test");
pinMode(A0, INPUT);
}
int oldContrast = 0;
long startTime = 0;
bool settingSent = false;
void loop()
{
int trimpot = averageAnalogRead(A0);
int newContrast = map(trimpot, 0, 1023, 0, 255); //Map this analog value down to 0-255
//Only send new contrast setting to display if the user changes the trimpot
if(newContrast != oldContrast)
{
Serial.print("nc: ");
Serial.println(newContrast);
oldContrast = newContrast; //Update
startTime = millis();
settingSent = false;
}
//Wait at least 100ms for user to stop turning trimpot
//OpenLCD displays the contrast setting for around 2 seconds so we can't send constant updates
if(millis() - startTime > 500 && settingSent == false)
{
//Send contrast setting
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(24); //Send contrast command
OpenLCD.write(newContrast);
settingSent = true;
Serial.print("New contrast: ");
Serial.println(newContrast);
}
delay(100); //Hang out for a bit
}
//Takes an average of readings on a given pin
//Returns the average
int averageAnalogRead(byte pinToRead)
{
byte numberOfReadings = 8;
unsigned int runningValue = 0;
for(int x = 0 ; x < numberOfReadings ; x++)
runningValue += analogRead(pinToRead);
runningValue /= numberOfReadings;
return(runningValue);
}
After uploading your sketch, you can now try adjusting the trimpot and watch the contrast change in real time. Here are a few examples that I see:
Contrast Setting: 0
Contrast Setting: 55
Contrast Setting: 80
Note, if you are not seeing any text in the LCD, make sure and try rotating to either extreme. If you are up above 100, then in some cases you may not see any text. Watching your serial monitor from your Arduino can be helpful as well. It will tell you the settings as you are sending them to the LCD. Here is some example serial debug that I see while I adjust the trimpot:
Serial UART: Example Code - Backlight Control
For this next example, you can use the same exact hardware setup as in the previous two examples (Serial Basic or Serial Contrast). To jump right in and start playing with backlight control, you can get the latest example code from the github repo or you can copy and paste the following code into your Arduino IDE:
language:cpp
/*
OpenLCD is an LCD with serial/I2C/SPI interfaces.
By: Nathan Seidle
SparkFun Electronics
Date: April 19th, 2015
License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).
OpenLCD gives the user multiple interfaces (serial, I2C, and SPI) to control an LCD. SerLCD was the original
serial LCD from SparkFun that ran on the PIC 16F88 with only a serial interface and limited feature set.
This is an updated serial LCD.
This example shows how to change the backlight brightness. We assume the module is currently at default 9600bps.
We use software serial because if OpenLCD is attached to an Arduino's hardware serial port during bootloading
it can cause problems for both devices.
Note: If OpenLCD gets into an unknown state or you otherwise can't communicate with it send 18 (0x12 or ctrl+r)
at 9600 baud while the splash screen is active and the unit will reset to 9600 baud.
Emergency reset: If you get OpenLCD stuck into an unknown baud rate, unknown I2C address, etc, there is a
safety mechanism built-in. Tie the RX pin to ground and power up OpenLCD. You should see the splash screen
then "System reset Power cycle me" and the backlight will begin to blink. Now power down OpenLCD and remove
the RX/GND jumper. OpenLCD is now reset to 9600bps with a I2C address of 0x72. Note: This feature can be
disabled if necessary. See *Ignore Emergency Reset* for more information.
To get this code to work, attached an OpenLCD to an Arduino Uno using the following pins:
RX (OpenLCD) to Pin 7 (Arduino)
VIN to 5V
GND to GND
Command cheat sheet:
ASCII / DEC / HEX
'|' / 124 / 0x7C - Put into setting mode
Ctrl+c / 3 / 0x03 - Change width to 20
Ctrl+d / 4 / 0x04 - Change width to 16
Ctrl+e / 5 / 0x05 - Change lines to 4
Ctrl+f / 6 / 0x06 - Change lines to 2
Ctrl+g / 7 / 0x07 - Change lines to 1
Ctrl+h / 8 / 0x08 - Software reset of the system
Ctrl+i / 9 / 0x09 - Enable/disable splash screen
Ctrl+j / 10 / 0x0A - Save currently displayed text as splash
Ctrl+k / 11 / 0x0B - Change baud to 2400bps
Ctrl+l / 12 / 0x0C - Change baud to 4800bps
Ctrl+m / 13 / 0x0D - Change baud to 9600bps
Ctrl+n / 14 / 0x0E - Change baud to 14400bps
Ctrl+o / 15 / 0x0F - Change baud to 19200bps
Ctrl+p / 16 / 0x10 - Change baud to 38400bps
Ctrl+q / 17 / 0x11 - Change baud to 57600bps
Ctrl+r / 18 / 0x12 - Change baud to 115200bps
Ctrl+s / 19 / 0x13 - Change baud to 230400bps
Ctrl+t / 20 / 0x14 - Change baud to 460800bps
Ctrl+u / 21 / 0x15 - Change baud to 921600bps
Ctrl+v / 22 / 0x16 - Change baud to 1000000bps
Ctrl+w / 23 / 0x17 - Change baud to 1200bps
Ctrl+x / 24 / 0x18 - Change the contrast. Follow Ctrl+x with number 0 to 255. 120 is default.
Ctrl+y / 25 / 0x19 - Change the TWI address. Follow Ctrl+x with number 0 to 255. 114 (0x72) is default.
Ctrl+z / 26 / 0x1A - Enable/disable ignore RX pin on startup (ignore emergency reset)
'-' / 45 / 0x2D - Clear display. Move cursor to home position.
/ 128-157 / 0x80-0x9D - Set the primary backlight brightness. 128 = Off, 157 = 100%.
/ 158-187 / 0x9E-0xBB - Set the green backlight brightness. 158 = Off, 187 = 100%.
/ 188-217 / 0xBC-0xD9 - Set the blue backlight brightness. 188 = Off, 217 = 100%.
For example, to change the baud rate to 115200 send 124 followed by 18.
*/
#include <SoftwareSerial.h>
SoftwareSerial OpenLCD(6, 7); //RX (not used), TX
byte counter = 0;
void setup()
{
Serial.begin(9600); //Begin local communication for debug statements
OpenLCD.begin(9600); //Begin communication with OpenLCD
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(158 + 0); //Set green backlight amount to 0%
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(188 + 0); //Set blue backlight amount to 0%
}
void loop()
{
//Control red backlight
Serial.println("Mono/Red backlight set to 0%");
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(128); //Set white/red backlight amount to 0%
delay(2000);
//Control red backlight
Serial.println("Mono/Red backlight set to 51%");
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(128 + 15); //Set white/red backlight amount to 51%
delay(2000);
//Control red backlight
Serial.println("Mono/Red backlight set to 100%");
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(128 + 29); //Set white/red backlight amount to 100%
delay(2000);
//The following green and blue backlight control only apply if you have an RGB backlight enabled LCD
all_off(); // turn off all backlights - see function below
//Control green backlight
Serial.println("Green backlight set to 51%");
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(158 + 15); //Set green backlight amount to 51%
delay(2000);
//Control green backlight
Serial.println("Green backlight set to 100%");
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(158 + 29); //Set green backlight amount to 100%
delay(2000);
all_off(); // turn off all backlights - see function below
//Control blue backlight
Serial.println("Blue backlight set to 51%");
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(188 + 15); //Set blue backlight amount to 51%
delay(2000);
//Control blue backlight
Serial.println("Blue backlight set to 100%");
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(188 + 29); //Set blue backlight amount to 100%
delay(2000);
all_off(); // turn off all backlights - see function below
}
void all_off(void)
{
// Set all colors to 0
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(128); //Set white/red backlight amount to 0%
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(158 + 0); //Set green backlight amount to 0%
OpenLCD.write('|'); //Put LCD into setting mode
OpenLCD.write(188 + 0); //Set blue backlight amount to 0%
delay(2000);
}
With this code running, you should see your backlight colors cycle through a pattern of 0%, then 50%, then 100%. It will show this for each color individually (Red, Green, and Blue). Here are some shots of what it looks like for me when I run the code. Note, yours may vary slightly due to your RAW input voltage and the temperature of your environment.
Tip: By mixing the amounts of each backlight color, you can create virtually any color you like. In the examples above, we are trying out each backlight on its own. For other colors, try combining different values of each backlight. For example, to make a purple, you can try RED:29, GREEN:5, and BLUE:25. To make a yellow, try RED:22, GREEN:29, and BLUE:5. To make white, turn them all on to 29 (aka 100%).
I2C: Hardware Hookup & Example Code - Basic
For I2C, there are only 2 communication lines you need to connect: SDA and CLK. But remember, these must be 3.3V logic. So if you are using a 5V Redboard like we are, then you’ll need to convert SDA and SCL from 5V to 3.3V. See the following Fritzing diagram for how you can wire this up:
After you’ve got your I2C lines wired up properly, you can get the latest example code from the github repo or you can copy and paste the following code into your Arduino IDE:
language:cpp
/*
OpenLCD is an LCD with Serial/I2C/SPI interfaces.
By: Nathan Seidle
SparkFun Electronics
Date: April 19th, 2015
License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).
This is example code that shows how to send data over I2C to the display.
Note: This code expects the display to be listening at the default I2C address. If your display is not at 0x72, you can
do a hardware reset. Tie the RX pin to ground and power up OpenLCD. You should see the splash screen
then "System reset Power cycle me" and the backlight will begin to blink. Now power down OpenLCD and remove
the RX/GND jumper. OpenLCD is now reset.
To get this code to work, attached an OpenLCD to an Arduino Uno using the following pins:
SCL (OpenLCD) to A5 (Arduino)
SDA to A4
VIN to 5V
GND to GND
Command cheat sheet:
ASCII / DEC / HEX
'|' / 124 / 0x7C - Put into setting mode
Ctrl+c / 3 / 0x03 - Change width to 20
Ctrl+d / 4 / 0x04 - Change width to 16
Ctrl+e / 5 / 0x05 - Change lines to 4
Ctrl+f / 6 / 0x06 - Change lines to 2
Ctrl+g / 7 / 0x07 - Change lines to 1
Ctrl+h / 8 / 0x08 - Software reset of the system
Ctrl+i / 9 / 0x09 - Enable/disable splash screen
Ctrl+j / 10 / 0x0A - Save currently displayed text as splash
Ctrl+k / 11 / 0x0B - Change baud to 2400bps
Ctrl+l / 12 / 0x0C - Change baud to 4800bps
Ctrl+m / 13 / 0x0D - Change baud to 9600bps
Ctrl+n / 14 / 0x0E - Change baud to 14400bps
Ctrl+o / 15 / 0x0F - Change baud to 19200bps
Ctrl+p / 16 / 0x10 - Change baud to 38400bps
Ctrl+q / 17 / 0x11 - Change baud to 57600bps
Ctrl+r / 18 / 0x12 - Change baud to 115200bps
Ctrl+s / 19 / 0x13 - Change baud to 230400bps
Ctrl+t / 20 / 0x14 - Change baud to 460800bps
Ctrl+u / 21 / 0x15 - Change baud to 921600bps
Ctrl+v / 22 / 0x16 - Change baud to 1000000bps
Ctrl+w / 23 / 0x17 - Change baud to 1200bps
Ctrl+x / 24 / 0x18 - Change the contrast. Follow Ctrl+x with number 0 to 255. 120 is default.
Ctrl+y / 25 / 0x19 - Change the TWI address. Follow Ctrl+x with number 0 to 255. 114 (0x72) is default.
Ctrl+z / 26 / 0x1A - Enable/disable ignore RX pin on startup (ignore emergency reset)
'-' / 45 / 0x2D - Clear display. Move cursor to home position.
/ 128-157 / 0x80-0x9D - Set the primary backlight brightness. 128 = Off, 157 = 100%.
/ 158-187 / 0x9E-0xBB - Set the green backlight brightness. 158 = Off, 187 = 100%.
/ 188-217 / 0xBC-0xD9 - Set the blue backlight brightness. 188 = Off, 217 = 100%.
For example, to change the baud rate to 115200 send 124 followed by 18.
*/
#include <Wire.h>
#define DISPLAY_ADDRESS1 0x72 //This is the default address of the OpenLCD
int cycles = 0;
void setup()
{
Wire.begin(); //Join the bus as master
//By default .begin() will set I2C SCL to Standard Speed mode of 100kHz
//Wire.setClock(400000); //Optional - set I2C SCL to High Speed Mode of 400kHz
Serial.begin(9600); //Start serial communication at 9600 for debug statements
Serial.println("OpenLCD Example Code");
//Send the reset command to the display - this forces the cursor to return to the beginning of the display
Wire.beginTransmission(DISPLAY_ADDRESS1);
Wire.write('|'); //Put LCD into setting mode
Wire.write('-'); //Send clear display command
Wire.endTransmission();
}
void loop()
{
cycles++; //Counting cycles! Yay!
// Serial.print("Cycle: "); //These serial.print statements take multiple miliseconds
// Serial.println(cycles);
i2cSendValue(cycles); //Send the four characters to the display
delay(50); //The maximum update rate of OpenLCD is about 100Hz (10ms). A smaller delay will cause flicker
}
//Given a number, i2cSendValue chops up an integer into four values and sends them out over I2C
void i2cSendValue(int value)
{
Wire.beginTransmission(DISPLAY_ADDRESS1); // transmit to device #1
Wire.write('|'); //Put LCD into setting mode
Wire.write('-'); //Send clear display command
Wire.print("Cycles: ");
Wire.print(value);
Wire.endTransmission(); //Stop I2C transmission
}
You may notice that this is very similar to the example above, Serial Basic. Well, that’s because it is doing the exact same thing but instead of Serial UART communication, it is sending the commands over I2C. If you’ve got it wired up correctly and the example code running, then you should see the “Hello World Counter:XX” displaying in your LCD screen.
SPI: Hardware Hookup & Example Code - Basic
Here’s how to wire up your Redboard to talk SPI to your LCD screen. Remember, convert those logic levels to 3.3Vs! Also note, you could choose a different output pin for the csPin, but in this example we are using D10.
With your hardware now hooked up, the following code is the SPI basic example - it simply writes some characters to the screen over SPI. It has a counter that will increment on each cycle of your main loop. It clears the screen at the top of each loop, so you simply see “Cycles: 1”, “Cycles: 2” and so on.
You can get the latest example code from the github repo or you can copy and paste the following code into your Arduino IDE:
language:cpp
/*
OpenLCD is an LCD with Serial/I2C/SPI interfaces.
By: Nathan Seidle
SparkFun Electronics
Date: April 19th, 2015
License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).
This is example code that shows how to send data over SPI to the display.
To get this code to work, attached an OpenLCD to an Arduino Uno using the following pins:
CS (OpenLCD) to 10 (Arduino)
SDI to 11
SDO to 12 (optional)
SCK to 13
VIN to 5V
GND to GND
Command cheat sheet:
ASCII / DEC / HEX
'|' / 124 / 0x7C - Put into setting mode
Ctrl+c / 3 / 0x03 - Change width to 20
Ctrl+d / 4 / 0x04 - Change width to 16
Ctrl+e / 5 / 0x05 - Change lines to 4
Ctrl+f / 6 / 0x06 - Change lines to 2
Ctrl+g / 7 / 0x07 - Change lines to 1
Ctrl+h / 8 / 0x08 - Software reset of the system
Ctrl+i / 9 / 0x09 - Enable/disable splash screen
Ctrl+j / 10 / 0x0A - Save currently displayed text as splash
Ctrl+k / 11 / 0x0B - Change baud to 2400bps
Ctrl+l / 12 / 0x0C - Change baud to 4800bps
Ctrl+m / 13 / 0x0D - Change baud to 9600bps
Ctrl+n / 14 / 0x0E - Change baud to 14400bps
Ctrl+o / 15 / 0x0F - Change baud to 19200bps
Ctrl+p / 16 / 0x10 - Change baud to 38400bps
Ctrl+q / 17 / 0x11 - Change baud to 57600bps
Ctrl+r / 18 / 0x12 - Change baud to 115200bps
Ctrl+s / 19 / 0x13 - Change baud to 230400bps
Ctrl+t / 20 / 0x14 - Change baud to 460800bps
Ctrl+u / 21 / 0x15 - Change baud to 921600bps
Ctrl+v / 22 / 0x16 - Change baud to 1000000bps
Ctrl+w / 23 / 0x17 - Change baud to 1200bps
Ctrl+x / 24 / 0x18 - Change the contrast. Follow Ctrl+x with number 0 to 255. 120 is default.
Ctrl+y / 25 / 0x19 - Change the TWI address. Follow Ctrl+x with number 0 to 255. 114 (0x72) is default.
Ctrl+z / 26 / 0x1A - Enable/disable ignore RX pin on startup (ignore emergency reset)
'-' / 45 / 0x2D - Clear display. Move cursor to home position.
/ 128-157 / 0x80-0x9D - Set the primary backlight brightness. 128 = Off, 157 = 100%.
/ 158-187 / 0x9E-0xBB - Set the green backlight brightness. 158 = Off, 187 = 100%.
/ 188-217 / 0xBC-0xD9 - Set the blue backlight brightness. 188 = Off, 217 = 100%.
For example, to change the baud rate to 115200 send 124 followed by 18.
*/
#include <SPI.h>
int csPin = 10; //You can use any output pin but for this example we use 10
int cycles = 0;
void setup()
{
pinMode(csPin, OUTPUT);
digitalWrite(csPin, HIGH); //By default, don't be selecting OpenLCD
SPI.begin(); //Start SPI communication
//SPI.beginTransaction(SPISettings(100000, MSBFIRST, SPI_MODE0));
SPI.setClockDivider(SPI_CLOCK_DIV128); //Slow down the master a bit
}
void loop()
{
cycles++; //Counting cycles! Yay!
//Send the clear display command to the display - this forces the cursor to return to the beginning of the display
digitalWrite(csPin, LOW); //Drive the CS pin low to select OpenLCD
SPI.transfer('|'); //Put LCD into setting mode
SPI.transfer('-'); //Send clear display command
digitalWrite(csPin, HIGH); //Release the CS pin to de-select OpenLCD
char tempString[50]; //Needs to be large enough to hold the entire string with up to 5 digits
sprintf(tempString, "Cycles: %d", cycles);
spiSendString(tempString);
//25ms works well
//15ms slight flickering
//5ms causes flickering
delay(250);
}
//Sends a string over SPI
void spiSendString(char* data)
{
digitalWrite(csPin, LOW); //Drive the CS pin low to select OpenLCD
for(byte x = 0 ; data[x] != '\0' ; x++) //Send chars until we hit the end of the string
SPI.transfer(data[x]);
digitalWrite(csPin, HIGH); //Release the CS pin to de-select OpenLCD
}
Troubleshooting
Random Character
If the display is powered up without the RX line connected to anything, the display may fill with strange characters. This is because the display is receiving random noise on the disconnected line. If you connect the RX line to a true TX port, this will not happen.
Faded Characters on Display
If the display is unreadable or washed out, the contrast may need to be adjusted. This is done in software, so you will need to send your display some contrast control commands via Serial UART, I2C or SPI. There is a specific example for each of these communication types inside the github repository here:
You can also follow along with the example in this tutorial above: click here.
Emergency Reset
If your LCD screen has entered an unknown state, or you are unable to communicate with it, it’s probably a good idea to try resetting everything back to default settings. The OpenLCD firmware has a built-in “emergency reset” feature. When the screen first boots up, the AVR on the back will watch its RX pin. If that pin is held LOW (aka tied to ground), for 2 seconds, then it will reset all settings to default. Most importantly, your baud rate will be set back to 9600. After the reset is complete, the screen will display the message “System Reset Power Cycle Me”, and flicker the backlight on and off repeatedly until you cycle power.
To perform an emergency reset, please be sure to follow these exact steps in this order:
Ensure your screen is OFF.
Tie RX to GND.
Power your screen.
Wait 2 seconds. Verify “System Reset” message.
Remove connection from RX to GND.
Cycle Power.
Now, please enjoy your default settings (including 9600 baud).
Firmware Update
To update the firmware on your LCD, you can use an FTDI Basic 3.3V - beefy model and the Arduino IDE software. The AVR on the back of your LCD actually has an Arduino-compatible bootloader. That said, it is slightly custom in that it was compiled for the 11.0592 crystal. This means you will need to install the SparkFun AVR Boards, and then select “Sparkfun OpenLCD” as your board type.
If you’ve made it this far, presumably you have the latest version of the Arduino IDE on your desktop. If you do not, please review our tutorial on installing the Arduino IDE.
Here’s the tutorial for installing custom board packages:
Note, you will need all of the files in the “https://github.com/sparkfun/OpenLCD/blob/master/firmware/OpenLCD/” directory. When you open the OpenLCD.ino sketch in arduino, the other necessary files will open as tabs.
OpenLCD.ino
Setting_Control.ino
System_Functions.ino
settings.h
Click the UPLOAD button in the IDE (the right facing arrow), and this will get the latest code onto your LCD!
Note, any of the settings stored in EEPROM memory (like baud, splash screen contents, backlight settings, etc.) will not be overwritten. If you are using a fresh IC (or you erased your EEPROM), then all of the defaults will return. To reset these settings to default, you must perform an “Emergency Reset”. The instructions for this are in another part of this troubleshooting section.
Using the Serial Enabled LCD on an Atmega32U4’s Hardware UART
If you are using the serial enabled LCD with an Atmega32U4-based Arduino (like a Pro Micro, Arduino Leonardo, Arduino LilyPad USB, etc), you might need to add a small delay in the setup before you can get it working with the hardware UART (pins 0 and 1). Here’s an example:
language:cpp
///test example using ATmega32U4's hardware UART and delay
void setup() {
delay(2000);//add delay so the ATmega32U4 can have a second before sending serial data to the LCD
Serial1.begin(9600);//set up the hardware UART baud
}
void loop() {
Serial1.print("print something");//send something to the serial enabled LCD
delay(50);
}
Software Serial for Arduino Due
Unfortunately, you are not able to use the serial enabled LCDs with an Arduino Due due the differences in how change interrupts are used for the ARM processor. The software serial library is not included in the Arduino Due’s tree:
Try using the other hardware serial UARTs that are not connected to the Arduino Due’s programming pins for uploading. Make sure to adjust the code for the hardware serial UART.
Resources and Going Further
Now that you’ve successfully got your OpenLCD up and running, it’s time to incorporate it into your own project! When it is complete (or even during the design and build phases) please share in comments section of this tutorial, we’d love to hear about it! We also like doing project highlights, so please don’t hesitate to reach out when it’s finished. Maybe we could even feature your project with a blog post and video!
Also, if you ran into any issues during this hookup guide, or something wasn’t crystal clear the first time you read it, please let us know in the comments section of this tutorial. We strive to make the best documentation possible, and really want to hear about any pain points you discovered. Thanks in advance!
For more information, check out the resources below:
OpenLCD GitHub Repo - OpenLCD design files, default firmware, and example code.
LCD User-Defined Graphics - If you would like to create a custom character, you would need to send a command byte before controlling the individual pixels in the character square.
Need some inspiration for your next project? Check out some of these related tutorials:
How to hook up and use the OpenSegment display shield. The OpenSegment is the big brother to the Serial 7-Segment Display. They run on the same firmware, however the OpenSegment is about twice as big.
Make bright, colorful displays using the 32x16, 32x32, and 32x64 RGB LED matrix panels. This hookup guide shows how to hook up these panels and control them with an Arduino.
This tutorial will show you how to combine a webcam, a 32x32 RGB LED panel, and a Teensy 3.1 to stream video from the webcam, pixelate it, and display it on the LED panel - LIVE.
The Single Supply Logic Level Converter combines a boost converter (TPS61200), adjustable voltage regulator (MIC5205), and logic level translator (TXB0104) into one board. It provides 5V to the high side of the TXB0104 and the low side is programmable to 3.3V, 2.5V and 1.8V. The default low side voltage is 3.3V. With this device you can use your 5V microcontroller with 3.3V sensors and vice versa without the need for a second power supply!
What makes this logic level converter truly special is the fact that you can supply it with 3.3V and it will boost it to 5V - meaning you can use your 3.3V system, and convert directly to another 5V sensor - and even power your sensor or other board! We will use a 3.3V microcontroller and a 5V sensor for the example. However, you can use still this board with a 5V microcontroller and a 3.3V sensor.
Required Materials
To follow along with this project tutorial, you will need the following materials to level shift between a 3.3V microcontroller with a 5V sensor. You may not need everything, depending on what you have. Add it to your cart, read through the guide, and adjust the cart as necessary.
Before we discuss hooking up the breakout, let’s go over some of the features of this board.
Pinout
The following table describes the pins that are broken out.
Pin
Description
VIN
Input Supply Voltage (3V - 5.5V)
GND
Ground
VOUT, 5V
Boost Converter's Voltage Output Set to 5V
VOUT, 3.3V
Regulated Voltage Output Set to 3.3V (can be adjusted depending on resistor)
A1-A4
Programmable VCCA Port for Lower TTL Logic Levels - Default = 3.3V
B1-B4
VCCB Port for Higher TTL Logic Levels - Set to 5V
Logic Level Shifter
The Single Supply Logic Level Converter breaks out Texas Instrument's TXB0104 module. The TXB0104 is a 4-bit, noninverting, bi-directional voltage-level translator with automatic direction sensing.
Each pin on this module is broken out for you to easily access ports A and B. Port A (A1-A4) is for low side TTL levels. This device’s VCCA is set to 3.3V by default but can easily be programmable with a resistor to 2.5V and 1.8V. Port B (B1-B4) is for high side TTL levels. VCCB is hard wired to 5V. VCCA should not exceed VCCB. Depending on which voltage is chosen for VCCA the data rate may vary.
Adjusting the Lower Voltage Side (i.e. VCCA)
To adjust the reference voltage for the low side, you will need an associated resistor value to adjust the MIC5205’s output voltage. Below is a table of calculated resistor values that can be used. For more information, check out equation 4-7 on page 11 of the datasheet.
VCCA
Resistor Value
3.3V
Default = 13kΩ
2.5V
22kΩ
1.8V
49kΩ
Heads up! While the datasheet states that the TXB0104 can translate voltages on the low side for VCCA between "1.2V to 3.6V" and high side for VCCB between "1.65V to 5.5V", the board only is capable of translating a minimum of about 1.58V on the low side due to the adjustable voltage regulator on the board.
Simply remove the default surface mount resistor with a blob of solder so that heat can be transferred to both terminals. Once heated, the surface mount resistor can be removed with tweezers or a gentle sweep of a soldering iron. Once removed, a resistor of your choice can be used to adjust the VCCA's reference voltage.
Note: Depending on the resistor value, you may need to add some resistors in series to achieve the exact resistor value. If necessary, try using heat shrink, wires, and the snappable protoboard when adding the resistors in series.
The TPS61200 buck/boost converter on the Single Supply Logic Level Converter takes an input between 3V - 5.5V (most likely from your microcontroller’s VCC pin) and regulates it to 5V. This output is also connected to the high side on VCCB for reference.
The output current of the TPS61200 depends on the input to output voltage ratio. The TPS61200 provides output currents up to 600 mA at 5V. The maximum average input current is limited to 1.5A. For more information, check out the datasheet.
The regulated 5V is then further regulated to 3.3V, which is connected to the low side on VCCA for reference. There is an option to reprogram VCCA's voltage using an external PTH resistor as explained earlier.
VCCA Reference Ground
⚡ Warning: The reference GND that you choose can affect the serial data being sent depending on how far your device is from the rest of the ground plane. You may notice some data not being sent correctly between your devices (like some giberrish or garbage data on a serial UART). It is recommended to use the GND pin by the lower VCCA side above the A1-A4 pins when you are reference ground on the low side.
Timing Requirements
Not all logic level converters are the same! Compared to the lower cost bi-directional logic level converter with BSS138, the single supply logic level converter with TXB0104 is able to achieve higher data rates. The speed is dependent on the reference voltage that is used for the low side voltage on VCCA. This is indicated by the table below and was taken from the datasheet. For more information, check out page 8 of the datasheet.
VCCA
Data Rate
Pulse Duration
1.5V
40 Mbps
25 ns
1.8V
60 Mbps
17 ns
2.5V
100 Mbps
10 ns
3.3V
100 Mbps
10 ns
Heads up! Remember, the single supply logic level converter can translate down to about 1.58V due to the voltage regulator attached to the VCCA's reference pin. You may get slightly above 40Mbps if you are referencing VCCA with about 1.58V.
Hardware Hookup
Grab some straight header pins, break the pins apart, and solder them to the single supply logic level converter.
This would also be a good time to solder headers to the 3.3V/8MHz Arduino Pro Mini if you have not already!
Circuit Diagram
Ready to start hooking everything up? Check out the circuit diagram below to connect the components together.
Having a hard time seeing the circuit? Click on the image for a closer look.
Example Code
Note: This example assumes you are using the latest version of the Arduino IDE on your desktop. If this is your first time using Arduino, please review our tutorial on installing the Arduino IDE.
The single supply logic level converter can be used to shift data in either direction. In this example, we are going to shift levels from a 3.3V Arduino microcontroller and a 5V sensor.
Level Shifting Between a 3.3V Microcontroller w/ 5V Sensor
Copy the code below and paste the following code into the Arduino IDE. Since we are using an Arduino Pro Mini 3.3V/8MHz, make sure that you are selecting the correct board selection. Additionally, make sure to have the correct COM port selected when uploading. When ready, upload the example code!
language:c
/*
Single Supply Logic Level Converter Hookup Guide
This project will beep continuosly with a frequency proportional
to distance. As objects get closer, the beep gets faster.
Hardware:
HC-SR04 Ultrasonic Sensor
Arduino Pro Mini 3.3V/8MHz
SparkFun Single Supply Logic Level Converter
Piezo Buzzer
*/
#define TRIG_PIN 10
#define ECHO_PIN 11
#define Beep 3
void setup() {
Serial.begin (9600);
pinMode(TRIG_PIN, OUTPUT);
digitalWrite(TRIG_PIN, LOW);
pinMode(Beep, OUTPUT);
}
void loop() {
unsigned long t1;
unsigned long t2;
unsigned long pulse_width;
float cm1;
// Hold the trigger pin high for at least 10 us
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
// Wait for pulse on echo pin
while (digitalRead(ECHO_PIN) == 0 );
// Measure how long the echo pin was held high (pulse width)
// Note: the micros() counter will overflow after ~70 min
t1 = micros();
while (digitalRead(ECHO_PIN) == 1);
t2 = micros();
pulse_width = t2 - t1;
// Calculate distance in centimeters.
cm1 = pulse_width*0.034/2;
if (cm1 >= 200 || cm1 <= 0){
Serial.println("Out of range");
}
else {
Serial.print(cm1);
Serial.println(" cm");
tone(Beep,528);
delay(100);
noTone(Beep);
delay(cm1);
}
}
Heads up! We found that when using this code with an ATmega32U4 (like the Pro Micro 3.3V/8MHz), it requires the user to toggle the reset button after a power cycle. The initial current draw to the boost converter is enough to cause the Pro Micro brown out.
After uploading, place your hand in front of the ultrasonic sensor. When your hand is within a certain range, the buzzer will begin beeping! As you move your hand toward the sensor, the buzzer will beep faster. Moving your hand away from the sensor will slow down the beeping until you are out of range.
Troubleshooting Warning: HVAC systems in offices and schools have been known to interfere with the performance of the ultrasonic distance sensor. If you are experiencing sporadic behavior from your circuit, check your surroundings. If there are numerous air ducts in the room you are using, try moving to a different room that does not have ducts. The airflow from these ducts can interfere with the waves sent from the sensor, creating noise and resulting in bad readings.
Resources and Going Further
Now that you know how to get the Single Supply Logic Level Converter up and running, it’s time to incorporate it into your own project!
For more on the Single Supply Logic Level Converter, check out the links below:
Python is generally more popular as a sequential programming language that is called from the command line interface (CLI). However, several frameworks exist that offer the ability to create slick graphical user interfaces (GUI) with Python. Combined with a single board computer, like the Raspberry Pi, this ability to build GUIs opens up new possibilities to create your own dashboard for watching metrics, explore virtual instrumentation (like LabVIEW), or make pretty buttons to control your hardware.
In this tutorial, we’ll go through the basics of Tkinter (pronounced “Tee-Kay-Inter”, as it’s the “TK Interface” framework), which is the default GUI package that comes bundled with Python. Other frameworks exist, such as wxPython, PyQt, and Kivy. While some of these might be more powerful, Tkinter is easy to learn, comes with Python, and shares the same open source license as Python.
Later in the tutorial, we’ll show how to control various pieces of hardware from a Tkinter GUI and then how to pull a Matplotlib graph into an interface. The first part of the tutorial (Tkinter basics) can be accomplished on any computer without special hardware. The parts that require controlling hardware or reading from a sensor will be shown on a Raspberry Pi.
Notice: This tutorial was written with Raspbian version "June 2018" and Python version 3.5.3. Other versions may affect how some of the steps in this guide are performed.
Required Materials
To work through the activities in this tutorial, you will need a few pieces of hardware:
Optional Materials
You have several options when it comes to working with the Raspberry Pi. Most commonly, the Pi is used as a standalone computer, which requires a monitor, keyboard, and mouse (listed below). To save on costs, the Pi can also be used as a headless computer (without a monitor, keyboard, and mouse).
Note that for this tutorial, you will need access to the Raspbian (or other Linux) graphical interface (known as the desktop). As a result, the two recommended ways to interact with your Pi is through a monitor, keyboard, and mouse or by using Virtual Network Computing (VNC).
Before diving in to Tkinter and connecting hardware, you’ll need to install and configure a few pieces of software. You can work through the first example with just Python, but you’ll need a Raspberry Pi for the other sections that involve connecting hardware (we’ll be using the RPi.GPIO and SMBus packages).
Tkinter comes with Python. If Python is installed, you will automatically have access to the the Tkinter package.
Please note: If you have trouble seeing any of the images throughout this tutorial, feel free to click on it to get a better look!
Hello, World!
Let’s start with a basic example. If you don’t have a Raspberry Pi, you can install Python on your computer to run this demo and the “Temperature Converter” experiment.
Run the Program
Copy the following into a new file. Save it, and give it a name like tkinter_hello.py.
language:python
import tkinter as tk
# Create the main window
root = tk.Tk()
root.title("My GUI")
# Create label
label = tk.Label(root, text="Hello, World!")
# Lay out label
label.pack()
# Run forever!
root.mainloop()
Run the program from the command line with python tkinter_hello.py. You should see a new window pop up with the phrase “Hello, World!” If you expand the window, you should see the phrase “My GUI” set in the title bar (you might have to expand the window to see it).
Code to Note
Let’s break down the relatively simple program. In the first line,
language:python
import tkinter as tk
we import the Tkinter module and shorten the name to tk. We do this to save us some typing in the rest of the code: we just need to type tk instead of tkinter.
In other Tkinter guides, you might see the import written as from tkinter import *. This says to import all classes, functions, and variables from the Tkinter package into the global space. While this might make typing easier (e.g. you would only need to type Tk() instead of tk.Tk()), it has the downside of cluttering your global workspace. In a larger application, you would need to keep track of all these global variables in your head, which can be quite difficult! For example, Tkinter has a variable named E (which we’ll see in a later example), and it’s much easier to remember that you mean Tkinter’s version of E (rather than E from another module) by having to write tk.E.
Next, we create a root window by calling Tkinter’s constructor, tk.Tk().
language:python
root = tk.Tk()
This automatically creates a graphical window with the necessary title bar, minimize, maximize, and close buttons (the size and location of these are based on your operating system’s preferences). We save a handle to this window in the variable root. This handle allows us to put other things in the window and reconfigure it (e.g. size) as necessary. For example, we can change the name in the title bar by calling the title method in the root window:
language:python
root.title("My GUI")
In this window, we can add various control elements, known as widgets. Widgets can include things like buttons, labels, text entry boxes, and so on. Here, we create a Label widget:
Notice that when we create any widget, we must pass it a reference to its parent object (the object that will contain our new widget). In this example, we want the root window to be the parent object of our label (i.e. root will own your label object). We also set the default message in the label to be the classic “Hello, World!”
When it comes to creating GUIs with Tkinter, it’s generally a good idea to create your widgets first and then lay out your widgets together within the same hierarchy. In this example, root is at the top of our hierarchy followed by our label object under that.
After creating our label, we lay it out using the pack() geometry manager.
language:python
label.pack()
A geometry manager is a piece of code that runs (as part of the Tkinter framework–we don’t see the backend parts) to organize our widgets based on criteria that we set. pack() just tells the geometry manager to put widgets in the same row or column. It’s usually the easiest to use if you just want one or a few widgets to appear (and not necessarily be nicely organized).
Finally, we tell Tkinter to start running:
language:python
root.mainloop()
Note that if we don’t call mainloop(), nothing will appear on our screen. This method says to take all the widgets and objects we created, render them on our screen, and respond to any interactions (such as button pushes, which we’ll cover in the next example). When we exit out of the main window (e.g. by pressing the close window button), the program will exit out of mainloop().
Tkinter Overview
This section is meant to give you an overview of the “building blocks” that are available in Tkinter and is in no way a complete list of classes, functions, and variables. The official Python docs and TkDocs offer a more comprehensive overview of the Tkinter package. Examples will be discussed in more details throughout this tutorial, but feel free to refer back to these reference tables as you build your own application.
Widgets
A widget is a controllable element within the GUI, and all Tkinter widgets have a shared set of methods. The Tkinter guide on effbot.org offers an easy-to-read reference for the core widget methods (these are the methods that all widgets have access to, regardless of which individual widget you might be using).
The following table shows all the core widgets with an example screenshot. Click on the widget name to view its reference documentation from effbot.org.
Container for one or more widgets split into multiple "panes." These panes can be resized by the user by dragging the serparator line(s) (known as "sashes").
A container for other widgets (much like the Frame widget) that appears in its own window. It can be useful for creating other application windows or pop-up notifications.
The code below was used to create the example widgets in the above table. Note that they are for demonstration purposes only, as much of the functionality has not been implemented (e.g. no functions for button pushes).
language:python
import tkinter as tk
# Create the main window
root = tk.Tk()
root.title("My GUI")
# Create a set of options and variable for the OptionMenu
options = ["Option 1", "Option 2", "Option 3"]
selected_option = tk.StringVar()
selected_option.set(options[0])
# Create a variable to store options for the Radiobuttons
radio_option = tk.IntVar()
###############################################################################
# Create widgets
# Create widgets
button = tk.Button(root, text="Button")
canvas = tk.Canvas(root, bg='white', width=50, height=50)
checkbutton = tk.Checkbutton(root, text="Checkbutton")
entry = tk.Entry(root, text="Entry", width=10)
frame = tk.Frame(root)
label = tk.Label(root, text="Label")
labelframe = tk.LabelFrame(root, text="LabelFrame", padx=5, pady=5)
listbox = tk.Listbox(root, height=3)
menu = tk.Menu(root)
# Menubutton: deprecated, use Menu instead
message = tk.Message(root, text="Message", width=50)
optionmenu = tk.OptionMenu(root, selected_option, *options)
panedwindow = tk.PanedWindow(root, sashrelief=tk.SUNKEN)
radiobutton_1 = tk.Radiobutton( root,
text="Option 1",
variable=radio_option,
value=1)
radiobutton_2 = tk.Radiobutton( root,
text="Option 2",
variable=radio_option,
value=2)
scale = tk.Scale(root, orient=tk.HORIZONTAL)
scrollbar = tk.Scrollbar(root)
spinbox = tk.Spinbox(root, values=(0, 2, 4, 10))
text = tk.Text(root, width=15, height=3)
toplevel = tk.Toplevel()
# Lay out widgets
button.pack(padx=5, pady=5)
canvas.pack(padx=5, pady=5)
checkbutton.pack(padx=5, pady=5)
entry.pack(padx=5, pady=5)
frame.pack(padx=5, pady=10)
label.pack(padx=5, pady=5)
labelframe.pack(padx=5, pady=5)
listbox.pack(padx=5, pady=5)
# Menu: See below for adding the menu bar at the top of the window
# Menubutton: deprecated, use Menu instead
message.pack(padx=5, pady=5)
optionmenu.pack(padx=5, pady=5)
panedwindow.pack(padx=5, pady=5)
radiobutton_1.pack(padx=5)
radiobutton_2.pack(padx=5)
scale.pack(padx=5, pady=5)
scrollbar.pack(padx=5, pady=5)
spinbox.pack(padx=5, pady=5)
text.pack(padx=5, pady=5)
# Toplevel: does not have a parent or geometry manager, as it is its own window
###############################################################################
# Add stuff to the widgets (if necessary)
# Draw something in the canvas
canvas.create_oval(5, 15, 35, 45, outline='blue')
canvas.create_line(10, 10, 40, 30, fill='red')
# Add a default value to the Entry widgets
entry.insert(0, "Entry")
# Create some useless buttons in the LabelFrame
button_yes = tk.Button(labelframe, text="YES")
button_no = tk.Button(labelframe, text="NO")
# Lay out buttons in the LabelFrame
button_yes.pack(side=tk.LEFT)
button_no.pack(side=tk.LEFT)
# Put some options in the Listbox
for item in ["Option 1", "Option 2", "Option 3"]:
listbox.insert(tk.END, item)
# Add some options to the menu
menu.add_command(label="File")
menu.add_command(label="Edit")
menu.add_command(label="Help")
# Add the menu bar to the top of the window
root.config(menu=menu)
# Create some labels to add to the PanedWindow
label_left = tk.Label(panedwindow, text="LEFT")
label_right = tk.Label(panedwindow, text="RIGHT")
# Add the labels to the PanedWindow
panedwindow.add(label_left)
panedwindow.add(label_right)
# Put some default text into the Text widgets
text.insert(tk.END, "I am a\nText widget")
# Create some widgets to put in the Toplevel widget (window)
top_label = tk.Label(toplevel, text="A Toplevel window")
top_button = tk.Button(toplevel, text="OK", command=toplevel.destroy)
# Lay out widgets in the Toplevel pop-up window
top_label.pack()
top_button.pack()
###############################################################################
# Run!
root.mainloop()
Geometry Managers
Just instantiating (creating) a widget does not necessarily mean that it will appear on the screen (with the exception of Toplevel(), which automatically creates a new window). To get the widget to appear, we need to tell the parent widget where to put it. To do that, we use one of Tkinter’s three geometery managers (also known as layout managers).
A geometry manager is some code that runs on the backend of Tkinter (we don’t interact with the geometry managers directly). We simply choose which geometry manager we want to use and give it some parameters to work with.
The three geometry managers are: grid, pack, and place. You should never mix geometry managers within the same hierarchy, but you can embed different managers within each other (for example, you can lay out a frame widget with grid in a Toplevel and then use pack to put different widgets within the frame).
Here is a table showing examples of the different geometry managers:
The grid manager places widgets in a table format with rows and columns. It will avoid overlapping widgets and will resize rows/columns as necessary to fit the widgets.
Pack is often the easiest geometry manager to use, as it just puts widgets in a single row or column (default). It "packs" the widgets by putting them side-by-side (or top-to-bottom).
The place geometry manager offers the most control but can be the most difficult to use. It allows you to specify the absolute or relative positions of the widgets in a window (or parent widget).
The code below was used to create the examples shown in the above table. Note that it creates 3 windows (1 with the Tk() constructor call and 2 others with Toplevel()) and uses different geometry managers to lay out 3 widgets in each.
language:python
import tkinter as tk
###############################################################################
# Grid layout example
# Create the main window (grid layout)
root = tk.Tk()
root.title("Grid")
# Create widgets
label_grid_1 = tk.Label(root, text="Widget 1", bg='red')
label_grid_2 = tk.Label(root, text="Widget 2", bg='green')
label_grid_3 = tk.Label(root, text="Widget 3", bg='blue')
# Lay out widgets in a grid
label_grid_1.grid(row=0, column=2)
label_grid_2.grid(row=1, column=1)
label_grid_3.grid(row=2, column=0)
###############################################################################
# Pack layout example
# Create another window for pack layout
window_pack = tk.Toplevel()
window_pack.title("Pack")
# Create widgets
label_pack_1 = tk.Label(window_pack, text="Widget 1", bg='red')
label_pack_2 = tk.Label(window_pack, text="Widget 2", bg='green')
label_pack_3 = tk.Label(window_pack, text="Widget 3", bg='blue')
# Lay out widgets with pack
label_pack_1.pack()
label_pack_2.pack()
label_pack_3.pack()
###############################################################################
# Place layout example
# Create another window for pack layout
window_place = tk.Toplevel()
window_place.title("Place")
# Create widgets
label_place_1 = tk.Label(window_place, text="Widget 1", bg='red')
label_place_2 = tk.Label(window_place, text="Widget 2", bg='green')
label_place_3 = tk.Label(window_place, text="Widget 3", bg='blue')
# Lay out widgets with pack
label_place_1.place(relx=0, rely=0.1)
label_place_2.place(relx=0.1, rely=0.2)
label_place_3.place(relx=0.7, rely=0.6)
###############################################################################
# Run!
root.mainloop()
Variables
If you want to dynamically change a widget’s displayed value or text (e.g. change the text in a Label), you need to use one of Tkinter’s Variables. This is because Python has no way of letting Tkinter know that a variable has been changed (known as tracing). As a result, we need to use a wrapper class for these variables.
Each Tkinter Variable has a get() and set() method so you can read and change with the Variable’s value. This page also gives you a list of other methods available to each Variable. You must choose the appropriate Variable for the values you plan to work with, and this table shows you which Variables you have available:
Manager
Description
Examples
BooleanVar
Works with Boolean values.
True, False
DoubleVar
Works with floating point values.
-68.0, 2.718281
IntVar
Works with integer values.
-3, 0, 42
StringVar
Works with strings.
"Hello", "world!"
If you want to see this in action, run the following code:
language:python
import tkinter as tk
# Declare global variables
counter = None
# This function is called whenever the button is pressed
def count():
global counter
# Increment counter by 1
counter.set(counter.get() + 1)
# Create the main window
root = tk.Tk()
root.title("Counter")
# Tkinter variable for holding a counter
counter = tk.IntVar()
counter.set(0)
# Create widgets (note that command is set to count and not count() )
label_counter = tk.Label(root, width=7, textvariable=counter)
button_counter = tk.Button(root, text="Count", command=count)
# Lay out widgets
label_counter.pack()
button_counter.pack()
# Run forever!
root.mainloop()
You should see a window appear with a number and button. Try pressing the button a few times to watch the number increment.
In the program, we create a button that calls the count() function whenever it is pressed. We also create an IntVar named counter and set its initial value to 0. Take a look at where we create the label:
You’ll notice that we assign our IntVar (counter) to the textvariable parameter. This tells Tkinter that whenever the counter variable is changed, the Label widget should automatically update its displayed text. This saves us from having to write a custom loop where we manually update all the displayed information!
From here, all we need to do is worry about updating the counter variable each time the button is pressed. In the count() function, we do that with:
language:python
counter.set(counter.get() + 1)
Notice that we can’t get the IntVar’s value by the normal means, and we can’t set it with the equals sign (=). We need to use the get() and set() methods, respectively.
Experiment 1: Temperature Converter
Before we connect any hardware, it can be helpful to try a more complicated example to get a feel for laying out a GUI and using Tkinter to build your vision. We’ll start with a simple example: a Celsius-to-Fahrenheit temperature converter.
The Vision
Before writing any code, pull out a piece of paper and a pencil (or dry erase board and marker). Sketch out how you want your GUI to look: where should labels go? Do you want text entry fields at the top or the bottom? Should you have a “Quit” button, or let the user click the window’s “Close” button?
Here is what I came up with for our simple converter application. Note that we can divide it into a simple 3x3 grid, as shown by the red lines (more or less–we might need to nudge some of the widgets to fit into their respective cells).
As a result, we can determine that using the grid manager would be the best for this layout.
Implementation
Copy the following code into your Python editor.
language:python
import tkinter as tk
# Declare global variables
temp_c = None
temp_f = None
# This function is called whenever the button is pressed
def convert():
global temp_c
global temp_f
# Convert Celsius to Fahrenheit and update label (through textvariable)
try:
val = temp_c.get()
temp_f.set((val * 9.0 / 5) + 32)
except:
pass
# Create the main window
root = tk.Tk()
root.title("Temperature Converter")
# Create the main container
frame = tk.Frame(root)
# Lay out the main container, specify that we want it to grow with window size
frame.pack(fill=tk.BOTH, expand=True)
# Allow middle cell of grid to grow when window is resized
frame.columnconfigure(1, weight=1)
frame.rowconfigure(1, weight=1)
# Variables for holding temperature data
temp_c = tk.DoubleVar()
temp_f = tk.DoubleVar()
# Create widgets
entry_celsius = tk.Entry(frame, width=7, textvariable=temp_c)
label_unitc = tk.Label(frame, text="°C")
label_equal = tk.Label(frame, text="is equal to")
label_fahrenheit = tk.Label(frame, textvariable=temp_f)
label_unitf = tk.Label(frame, text="°F")
button_convert = tk.Button(frame, text="Convert", command=convert)
# Lay out widgets
entry_celsius.grid(row=0, column=1, padx=5, pady=5)
label_unitc.grid(row=0, column=2, padx=5, pady=5, sticky=tk.W)
label_equal.grid(row=1, column=0, padx=5, pady=5, sticky=tk.E)
label_fahrenheit.grid(row=1, column=1, padx=5, pady=5)
label_unitf.grid(row=1, column=2, padx=5, pady=5, sticky=tk.W)
button_convert.grid(row=2, column=1, columnspan=2, padx=5, pady=5, sticky=tk.E)
# Place cursor in entry box by default
entry_celsius.focus()
# Run forever!
root.mainloop()
Give it a name like tkinter_temp_converter.py, and run it. You should see a new window appear with an area to type in a number (representing degrees Celsius). Press the “Convert” button, and the equivalent temperature in Fahrenheit should appear in the label next to “°F.”
Code to Note
Let’s break down some of the code we saw in the previous example. After importing Tkinter, we define our convert() function. This is a callback, as we pass the function as an argument to our button, and Tkinter calls our function whenever a button press event occurs (i.e. we never call convert() directly).
While not completely necessary, you’ll notice that we declared temp_c and temp_f as global variables, as we want to be able to access them from within the function, and they were not passed in as parameters. Additionally, you’ll see that we calculate temp_f within a try/except block. If the user enters a string (instead of numbers), our conversion will fail, so we just tell Python to ignore the exception and don’t perform any calculation on the incorrectly typed data.
The next thing we do is create a frame within our main window and use the fill and expand parameters to allow it to grow with the window size.
language:python
# Create the main container
frame = tk.Frame(root)
# Lay out the main container, specify that we want it to grow with window size
frame.pack(fill=tk.BOTH, expand=True)
In our previous examples, we had been placing our widgets directly in the main window. This is generally not considered good practice, so we pack a frame within the window first, and then put our widgets within the frame. By doing this, we can more easily control how the widgets behave when we resize the window.
Before creating our widgets, we tell the frame that it should expand with the window if the user resizes it:
language:python
# Allow middle cell of grid to grow when window is resized
frame.columnconfigure(1, weight=1)
frame.rowconfigure(1, weight=1)
Note that because we used the pack() manager for the frame, we must tell column/rowconfigure that the location of the cell is (1, 1). If were were using these methods with a grid() manager, we could specify different rows and columns. The weight parameter tells the geometry manager how it should resize the given row/column proportionally to all the others. By setting this to 1 for each configure method, we’re telling Tkinter to just resize the whole frame to fill the window. To learn more, see the Handling Resize section of this TkDocs article.
After that, we create our Tkinter Variables, temp_c and temp_f and create all of our widgets that belong in the frame. Note that we assign our convert() function to our button with command=convert parameter assignment. By doing this, we tell Tkinter that we want to call the convert() function whenever the button is pressed.
We then lay out each of the widgets using the grid geometry manager. We go through each widget and assign it to a cell location. There is nothing in the top-left cell (0, 0), so we leave it blank. We put the text entry widget in the next column over (0, 1) followed by the units (0, 2). We replicate this process for the next row (row 1) with the label for “is equal to,” the solution, and the units for Fahrenheit. Finally, we add the convert button to the bottom-right. However, because the button is wider than the unit labels, we have it take up two cells: (2, 1) and (2, 2). We do that with columnspan=2, which works like the “merge” command in most modern spreadsheet programs.
Note that in some of the .grid() layout calls, we use the sticky parameter. This tells the geometry manager how to put the widget in its cell. By default (i.e. not using sticky), the widget will be centered within the cell. tk.W says that the widget should be left-aligned in its cell, and tk.E says it should be right-alighted. You can use the following anchor constants to align your widget:
Finally, before running our mainloop, we tell Tkinter that the Entry box should have focus.
language:python
# Place cursor in entry box by default
entry_celsius.focus()
This places the cursor in the entry box so the user can immediately begin typing without having to click on the Entry widget.
Experiment 2: Lights and Buttons
Let’s connect some hardware! If you want to dig deeper into user interface design, a book on design theory, like this one, might be a good place to start. By controlling hardware, we can begin to connect GUI design to the real world. Want to make your own Nest-style thermostat? This is a good place to start.
Hardware Connections
We’ll start with a few basic examples that show how to control an LED and respond to a physical button push. Connect the LED, button, and resistors as shown in the diagrams.
Having trouble seeing the diagrams? Click on them to see the full-size version!
If you have a Pi Wedge, it can make connecting to external hardware on a breadboard easier. If you don’t, you can still connect directly to the Raspberry Pi with jumper wires.
Connecting through a Pi Wedge:
Connecting directly to the Raspberry Pi:
Code Part 1: LED Light Switch
Depending on your version of Raspbian, the RPi.GPIO package may or may not be already installed (e.g. Raspbian Lite does not come with some Python packages pre-installed). In a terminal, enter the following:
language:bash
pip install rpi.gpio
Copy the following code into a new file:
language:python
import tkinter as tk
from tkinter import font
import RPi.GPIO as GPIO
# Declare global variables
button_on = None
button_off = None
# Pin definitions
led_pin = 12
# This gets called whenever the ON button is pressed
def on():
global button_on
global button_off
# Disable ON button, enable OFF button, and turn on LED
button_on.config(state=tk.DISABLED, bg='gray64')
button_off.config(state=tk.NORMAL, bg='gray99')
GPIO.output(led_pin, GPIO.HIGH)
# This gets called whenever the OFF button is pressed
def off():
global button_on
global button_off
# Disable OFF button, enable ON button, and turn off LED
button_on.config(state=tk.NORMAL, bg='gray99')
button_off.config(state=tk.DISABLED, bg='gray64')
GPIO.output(led_pin, GPIO.LOW)
# Use "GPIO" pin numbering
GPIO.setmode(GPIO.BCM)
# Set LED pin as output and turn it off by default
GPIO.setup(led_pin, GPIO.OUT)
GPIO.output(led_pin, GPIO.LOW)
# Create the main window
root = tk.Tk()
root.title("LED Switch")
# Create the main container
frame = tk.Frame(root)
# Lay out the main container
frame.pack()
# Create widgets
button_font = font.Font(family='Helvetica', size=24, weight='bold')
button_on = tk.Button(frame, text="ON", width=4, command=on,
state=tk.NORMAL, font=button_font, bg='gray99')
button_off = tk.Button(frame, text="OFF", width=4, command=off,
state=tk.DISABLED, font=button_font, bg='gray64')
# Lay out widgets
button_on.grid(row=0, column=0)
button_off.grid(row=1, column=0)
# Run forever!
root.mainloop()
# Neatly release GPIO resources once window is closed
GPIO.cleanup()
Save your file with a name like tkinter_switch.py and run it with python tkinter_switch.py. You should see a new window pop up with two buttons: ON and OFF. OFF should be grayed out, so try pressing ON. The LED should turn on and OFF should now be the only available button to press. Press it, and the LED should turn off. This is the software version of a light switch!
Code to Note:
In the on() and off() function definitions, we enable and disable the buttons by using the .config() method. For example:
.config() allows us to dynamically change the attributes of widgets even after they’ve been created. In our switch example, we change their state and bg (background color) to create the effect of the switch being active or “grayed out.”
You might also notice that we use the font module within Tkinter to create a custom font for the buttons. This allows us to change the typeface as well as make the font bigger and bolder. We then assign this new font to the buttons' text with font=button_font.
At the end of the code, we place the following line after our root.mainloop():
language:python
GPIO.cleanup()
This line tells Linux to release the resources it was using to handle all the pin toggling that we were doing after we close the main window. Without this line, we would get a warning next time we ran the program (or tried to use the RPi.GPIO module).
Code Part 2: Dimmer Switch
Since we proved we can turn an LED on and off, let’s try dimming it. In fact, let’s make a virtual dimmer switch! In a new file, copy in the following code:
language:python
import tkinter as tk
import RPi.GPIO as GPIO
# Declare global variables
pwm = None
# Pin definitions
led_pin = 12
# This gets called whenever the scale is changed--change brightness of LED
def dim(i):
global pwm
# Change the duty cycle based on the slider value
pwm.ChangeDutyCycle(float(i))
# Use "GPIO" pin numbering
GPIO.setmode(GPIO.BCM)
# Set LED pin as output
GPIO.setup(led_pin, GPIO.OUT)
# Initialize pwm object with 50 Hz and 0% duty cycle
pwm = GPIO.PWM(led_pin, 50)
pwm.start(0)
# Create the main window and set initial size
root = tk.Tk()
root.title("LED Dimmer")
root.geometry("150x300")
# Create the main container
frame = tk.Frame(root)
# Lay out the main container (center it in the window)
frame.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
# Create scale widget
scale = tk.Scale( frame,
orient=tk.VERTICAL,
from_=100,
to=0,
length=200,
width=50,
sliderlength=50,
showvalue=False,
command=dim )
# Lay out widget in frame
scale.pack()
# Run forever!
root.mainloop()
# Stop, cleanup, and exit when window is closed
pwm.stop()
GPIO.cleanup()
Give the file a name like tkinter_dimmer.py and run it. You should see a Scale widget pop up in a new window. Try sliding it, and you should see the attached LED get brighter.
Code to Note:
When constructing our Scale widget, we need to set a good number of attributes:
By default, scales appear horizontally, so we use orient=tk.VERTICAL to have it work like a dimmer switch you might find in a house. Next, the Scale will count from 0 to 100 by default, but 0 will be at the top (in the vertical orientation). As a result, we swap the from_ and to parameters so that 0 starts at the bottom. Also, notice that from_ has an underscore after it; from is a reserved keyword in Python, so Tkinter had to name it something else. We use 0 through 100, as those are the acceptable values for the pwm.ChangeDutyCycle() method parameter.
We can adjust the size and shape of the Scale with length and width. We made it a little bigger than default so that you can manipulate it more easily on a touchscreen. The slider part of the Scale (the part you click and drag) can by sized with sliderlength. Once again, we make the slider larger so that it’s easier to work with on a touchscreen.
By default, the numerical value of the Scale is shown next to the slider. We want to turn that off to provide a cleaner interface. The user only needs to drag the slider to a relative position; the exact number does not quite translate to perceived brightness anyway.
Additionally, by setting command=dim, we tell Tkinter that we want to call the dim() function every time the slider is moved. This allows us to set up a callback where we can adjust the PWM value of the LED each time the user interacts with the Scale.
Finally, notice that the dim(i) function now takes a parameter, i. Unlike our button functions in previous examples (which do not take any parameters), the Scale widget requires its callback (as set by command=dim) to accept one parameter. This parameter is the value of the slider; each time the slider is moved, dim(i) is called and i is set to the value of the slider (0-100 in this case).
Code Part 3: Respond to a Button Push
Blinking LEDs is fun, but what about responding to some kind of user input (I don’t mean on the screen)? Responding to physical button pushes can be important if you don’t want your user to have to use a touchscreen or mouse and keyboard. In a new file, enter the following code:
language:python
import tkinter as tk
import RPi.GPIO as GPIO
# Declare global variables
root = None
canvas = None
circle = None
# Pins definitions
btn_pin = 4
# Parameters
canvas_width = 100
canvas_height = 100
radius = 30
# Check on button state and update circle color
def poll():
global root
global btn_pin
global canvas
global circle
# Make circle red if button is pressed and black otherwise
if GPIO.input(btn_pin):
canvas.itemconfig(circle, fill='black')
else:
canvas.itemconfig(circle, fill='red')
# Schedule the poll() function for another 10 ms from now
root.after(10, poll)
# Set up pins
GPIO.setmode(GPIO.BCM)
GPIO.setup(btn_pin, GPIO.IN)
# Create the main window
root = tk.Tk()
root.title("Button Responder")
# Create the main container
frame = tk.Frame(root)
# Lay out the main container (center it in the window)
frame.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
# Create a canvas widget
canvas = tk.Canvas(frame, width=canvas_width, height=canvas_height)
# Lay out widget in frame
canvas.pack()
# Calculate top left and bottom right coordinates of the circle
x0 = (canvas_width / 2) - radius
y0 = (canvas_height / 2) - radius
x1 = (canvas_width / 2) + radius
y1 = (canvas_height / 2) + radius
# Draw circle on canvas
circle = canvas.create_oval(x0, y0, x1, y1, width=4, fill='black')
# Schedule the poll() function to be called periodically
root.after(10, poll)
# Run forever
root.mainloop()
Save it (with a name like tkinter_button.py), and run it. You should see a black circle appear in the middle of your window. When you press the button on the breadboard, the circle should turn red. Neat.
Code to Note:
The most important part of this example is how to poll for a physical button press (or any other hardware interaction on the Pi’s GPIO pins). You might have noticed that root.mainloop() is blocking. That is, it takes over your program, and your Python script essentially stops running while it sits in the mainloop method. In reality, there is a lot going on in the background: Tkinter is looking for and responding to events, resizing widgets as necessary, and drawing things to your screen. But for our purposes, it looks like the script just sits there while the GUI is displayed (if the user closes the main GUI window, root.mainloop() will exit).
Since we have a blocking method call, how do we check for a button push? That’s where the .after() method comes into play. We set up the poll() function to be called every 10 ms without being in any sort of loop. Since we’ve moved into the realm of event-driven programming, we must do everything with callbacks!
language:python
root.after(10, poll)
This line says that after 10 ms, the poll() function should be called. The bulk of the poll() function is fairly straightforward: we see if the button has been pressed (the button’s GPIO pin is low), and we change the color of the circle in the canvas if it is. However, the end of the function is very important:
language:python
root.after(10, poll)
That’s right, at the very end of the poll() function, we tell our main window that it should call the poll() function again! This makes it so that we are checking the state of the button many times every second. We will use this concept of polling for hardware information (separately from the GUI mainloop()) in the next experiment.
We created a circle on the screen by drawing on the canvas with:
language:python
circle = canvas.create_oval(x0, y0, x1, y1, width=4, fill='black')
In this next experiment, we’re going to connect a couple of I2C sensors and display their information in real time on our monitor. We start by just showing the sensor’s numerical values and then bring in Matplotlib to create a live updating graph of that data. Note that these are just example sensors; feel free to use whatever sensors you’d like for your particular application.
To simplify our I2C reading and writing, we’re going to copy in Python modules to read data from the TMP102 and APDS-9301 sensors. Open a new file named tmp102.py:
language:bash
nano tmp102.py
Copy in the following Python code:
language:python
import smbus
# Module variables
i2c_ch = 1
bus = None
# TMP102 address on the I2C bus
i2c_address = 0x48
# Register addresses
reg_temp = 0x00
reg_config = 0x01
# Calculate the 2's complement of a number
def twos_comp(val, bits):
if (val & (1 << (bits - 1))) != 0:
val = val - (1 << bits)
return val
# Read temperature registers and calculate Celsius
def read_temp():
global bus
# Read temperature registers
val = bus.read_i2c_block_data(i2c_address, reg_temp, 2)
temp_c = (val[0] << 4) | (val[1] >> 5)
# Convert to 2s complement (temperatures can be negative)
temp_c = twos_comp(temp_c, 12)
# Convert registers value to temperature (C)
temp_c = temp_c * 0.0625
return temp_c
# Initialize communications with the TMP102
def init():
global bus
# Initialize I2C (SMBus)
bus = smbus.SMBus(i2c_ch)
# Read the CONFIG register (2 bytes)
val = bus.read_i2c_block_data(i2c_address, reg_config, 2)
# Set to 4 Hz sampling (CR1, CR0 = 0b10)
val[1] = val[1] & 0b00111111
val[1] = val[1] | (0b10 << 6)
# Write 4 Hz sampling back to CONFIG
bus.write_i2c_block_data(i2c_address, reg_config, val)
# Read CONFIG to verify that we changed it
val = bus.read_i2c_block_data(i2c_address, reg_config, 2)
Save the code with ctrl + x, press y, and press enter. This module allows us to call init() and read_temp() functions to initialize and read temperature data from the TMP102.
Similarly, we need to create a module for our APDS-9301. Create a new file named apds9301.py:
language:bash
nano apds9301.py
Copy in the following code:
language:python
import smbus
# Module variables
i2c_ch = 1
bus = None
# APDS-9301 address on the I2C bus
apds9301_addr = 0x39
# Register addresses
apds9301_control_reg = 0x80
apds9301_timing_reg = 0x81
apds9301_data0low_reg = 0x8C
apds9301_data1low_reg = 0x8E
# Initialize communications and turn on the APDS-9301
def init():
global bus
# Initialize I2C (SMBus)
bus = smbus.SMBus(i2c_ch)
# Read the CONTROL register (1 byte)
val = bus.read_i2c_block_data(apds9301_addr, apds9301_control_reg, 1)
# Set POWER to on in the CONTROL register
val[0] = val[0] & 0b11111100
val[0] = val[0] | 0b11
# Enable the APDS-9301 by writing back to CONTROL register
bus.write_i2c_block_data(apds9301_addr, apds9301_control_reg, val)
# Read light data from sensor and calculate lux
def read_lux():
global bus
# Read channel 0 light value and combine 2 bytes into 1 number
val = bus.read_i2c_block_data(apds9301_addr, apds9301_data0low_reg, 2)
ch0 = (val[1] << 8) | val[0]
# Read channel 1 light value and combine 2 bytes into 1 number
val = bus.read_i2c_block_data(apds9301_addr, apds9301_data1low_reg, 2)
ch1 = (val[1] << 8) | val[0]
# Make sure we don't divide by 0
if ch0 == 0.0:
return 0.0
# Calculate ratio of ch1 and ch0
ratio = ch1 / ch0
# Assume we are using the default 13.7 ms integration time on the sensor
# So, scale raw light values by 1/0.034 as per the datasheet
ch0 *= 1 / 0.034
ch1 *= 1 / 0.034
# Assume we are using the default low gain setting
# So, scale raw light values by 16 as per the datasheet
ch0 *= 16;
ch1 *= 16;
# Calculate lux based on the ratio as per the datasheet
if ratio <= 0.5:
return (0.0304 * ch0) - ((0.062 * ch0) * ((ch1/ch0) ** 1.4))
elif ratio <= 0.61:
return (0.0224 * ch0) - (0.031 * ch1)
elif ratio <= 0.8:
return (0.0128 * ch0) - (0.0153 * ch1)
elif ratio <= 1.3:
return (0.00146 * ch0) - (0.00112*ch1)
else:
return 0.0
Save and exit with ctrl + x, y, and enter. Like our tmp102 module, we can call init() and read_lux() to initialize and read the ambient light values from the APDS-9301 sensor.
Note: Make sure that tmp102.py and apds9301.py are in the same directory as your main application code. Otherwise, your import statements will not be able to find your modules.
Code Part 1: Fullscreen Numerical Dashboard
Let’s start by making a simple display that takes up the full screen and shows the numerical temperature and ambient light values. Copy the following code into a new file:
language:python
import tkinter as tk
import tkinter.font as tkFont
import tmp102
import apds9301
###############################################################################
# Parameters and global variables
# Declare global variables
root = None
dfont = None
frame = None
temp_c = None
lux = None
# Global variable to remember if we are fullscreen or windowed
fullscreen = False
###############################################################################
# Functions
# Toggle fullscreen
def toggle_fullscreen(event=None):
global root
global fullscreen
# Toggle between fullscreen and windowed modes
fullscreen = not fullscreen
root.attributes('-fullscreen', fullscreen)
resize()
# Return to windowed mode
def end_fullscreen(event=None):
global root
global fullscreen
# Turn off fullscreen mode
fullscreen = False
root.attributes('-fullscreen', False)
resize()
# Automatically resize font size based on window size
def resize(event=None):
global dfont
global frame
# Resize font based on frame height (minimum size of 12)
# Use negative number for "pixels" instead of "points"
new_size = -max(12, int((frame.winfo_height() / 10)))
dfont.configure(size=new_size)
# Read values from the sensors at regular intervals
def poll():
global root
global temp_c
global lux
# Update labels to display temperature and light values
try:
val = round(tmp102.read_temp(), 2)
temp_c.set(val)
val = round(apds9301.read_lux(), 1)
lux.set(val)
except:
pass
# Schedule the poll() function for another 500 ms from now
root.after(500, poll)
###############################################################################
# Main script
# Create the main window
root = tk.Tk()
root.title("The Big Screen")
# Create the main container
frame = tk.Frame(root)
# Lay out the main container (expand to fit window)
frame.pack(fill=tk.BOTH, expand=1)
# Variables for holding temperature and light data
temp_c = tk.DoubleVar()
lux = tk.DoubleVar()
# Create dynamic font for text
dfont = tkFont.Font(size=-24)
# Create widgets
label_temp = tk.Label(frame, text="Temperature:", font=dfont)
label_celsius = tk.Label(frame, textvariable=temp_c, font=dfont)
label_unitc = tk.Label(frame, text="°C", font=dfont)
label_light = tk.Label(frame, text="Light:", font=dfont)
label_lux = tk.Label(frame, textvariable=lux, font=dfont)
label_unitlux = tk.Label(frame, text="lux", font=dfont)
button_quit = tk.Button(frame, text="Quit", font=dfont, command=root.destroy)
# Lay out widgets in a grid in the frame
label_temp.grid(row=0, column=0, padx=5, pady=5, sticky=tk.E)
label_celsius.grid(row=0, column=1, padx=5, pady=5, sticky=tk.E)
label_unitc.grid(row=0, column=2, padx=5, pady=5, sticky=tk.W)
label_light.grid(row=1, column=0, padx=5, pady=5, sticky=tk.E)
label_lux.grid(row=1, column=1, padx=5, pady=5, sticky=tk.E)
label_unitlux.grid(row=1, column=2, padx=5, pady=5, sticky=tk.W)
button_quit.grid(row=2, column=2, padx=5, pady=5)
# Make it so that the grid cells expand out to fill window
for i in range(0, 3):
frame.rowconfigure(i, weight=1)
for i in range(0, 3):
frame.columnconfigure(i, weight=1)
# Bind F11 to toggle fullscreen and ESC to end fullscreen
root.bind('<F11>', toggle_fullscreen)
root.bind('<Escape>', end_fullscreen)
# Have the resize() function be called every time the window is resized
root.bind('<Configure>', resize)
# Initialize our sensors
tmp102.init()
apds9301.init()
# Schedule the poll() function to be called periodically
root.after(500, poll)
# Start in fullscreen mode and run
toggle_fullscreen()
root.mainloop()
Save the file with a name like tkinter_fullscreen.py and run it. Your entire screen should be taken over by the GUI, and you should see the local ambient temperature and light values displayed. Try covering the light sensor or breathing on the temperature sensor to change their values. Press esc to exit fullscreen or press F11 to toggle fullscreen on and off.
Code to Note:
To control having our application take up the entire screen, we use the following method:
These bind the key presses F11 and esc to the toggle_fullscreen() and end_fullscreen() functions, respectively. These allow the user to control if the application takes up the entire screen or is in a window.
We also use the rowconfigure() and columnconfigure() methods again to control how the grid cells resize within the window. We combine this with a dynamic font:
language:python
dfont = tkFont.Font(size=-24)
Note that the negative number (-24) means we want to specify the font size in pixels instead of “points.” We also have our resize() function called every time the window is resized with the following:
language:python
root.bind('<Configure>', resize)
In our resize() function, we calculate a new font size based on the height of the resized frame with:
This says that the new font size should be the height of the frame divided by 10, but no smaller than 12. We turn it into a negative value, as we want to specify font height in pixels instead of points (once again). We then set the new font size with:
language:python
dfont.configure(size=new_size)
Try it! With the application running, press esc to exit fullscreen mode and try resizing the window. You should see the text grow and shrink as necessary. It’s not perfect, as certain aspect ratios will cut off portions of the text, but it should work without a problem in fullscreen mode (the intended application).
If you are using a touchscreen, you might not have an easy way for users to resize the window or quit out of the application (in some instances, that might be a good thing, but for our example, we want users to be able to exit). To accomplish this, we add a “Quit” button to our GUI:
We assign the callback function to be root.destroy. This is a built-in method within Tkinter that says to close the associated window and exit out of mainloop.
You’ll also notice that we are relying on the after() method again to call our poll() function at regular intervals.
Code Part 2: Complete Dashboard with Plotting
Now it’s time to get fancy. Let’s take the basic dashboard concept and add plotting. To do this, we’ll need to pull in the Matplotlib package. If you have not already installed it, run the following commands in a terminal:
language:python
import datetime as dt
import tkinter as tk
import tkinter.font as tkFont
import matplotlib.figure as figure
import matplotlib.animation as animation
import matplotlib.dates as mdates
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tmp102
import apds9301
###############################################################################
# Parameters and global variables
# Parameters
update_interval = 60000 # Time (ms) between polling/animation updates
max_elements = 1440 # Maximum number of elements to store in plot lists
# Declare global variables
root = None
dfont = None
frame = None
canvas = None
ax1 = None
temp_plot_visible = None
# Global variable to remember various states
fullscreen = False
temp_plot_visible = True
light_plot_visible = True
###############################################################################
# Functions
# Toggle fullscreen
def toggle_fullscreen(event=None):
global root
global fullscreen
# Toggle between fullscreen and windowed modes
fullscreen = not fullscreen
root.attributes('-fullscreen', fullscreen)
resize(None)
# Return to windowed mode
def end_fullscreen(event=None):
global root
global fullscreen
# Turn off fullscreen mode
fullscreen = False
root.attributes('-fullscreen', False)
resize(None)
# Automatically resize font size based on window size
def resize(event=None):
global dfont
global frame
# Resize font based on frame height (minimum size of 12)
# Use negative number for "pixels" instead of "points"
new_size = -max(12, int((frame.winfo_height() / 15)))
dfont.configure(size=new_size)
# Toggle the temperature plot
def toggle_temp():
global canvas
global ax1
global temp_plot_visible
# Toggle plot and axis ticks/label
temp_plot_visible = not temp_plot_visible
ax1.collections[0].set_visible(temp_plot_visible)
ax1.get_yaxis().set_visible(temp_plot_visible)
canvas.draw()
# Toggle the light plot
def toggle_light():
global canvas
global ax2
global light_plot_visible
# Toggle plot and axis ticks/label
light_plot_visible = not light_plot_visible
ax2.get_lines()[0].set_visible(light_plot_visible)
ax2.get_yaxis().set_visible(light_plot_visible)
canvas.draw()
# This function is called periodically from FuncAnimation
def animate(i, ax1, ax2, xs, temps, lights, temp_c, lux):
# Update data to display temperature and light values
try:
new_temp = round(tmp102.read_temp(), 2)
new_lux = round(apds9301.read_lux(), 1)
except:
pass
# Update our labels
temp_c.set(new_temp)
lux.set(new_lux)
# Append timestamp to x-axis list
timestamp = mdates.date2num(dt.datetime.now())
xs.append(timestamp)
# Append sensor data to lists for plotting
temps.append(new_temp)
lights.append(new_lux)
# Limit lists to a set number of elements
xs = xs[-max_elements:]
temps = temps[-max_elements:]
lights = lights[-max_elements:]
# Clear, format, and plot light values first (behind)
color = 'tab:red'
ax1.clear()
ax1.set_ylabel('Temperature (C)', color=color)
ax1.tick_params(axis='y', labelcolor=color)
ax1.fill_between(xs, temps, 0, linewidth=2, color=color, alpha=0.3)
# Clear, format, and plot temperature values (in front)
color = 'tab:blue'
ax2.clear()
ax2.set_ylabel('Light (lux)', color=color)
ax2.tick_params(axis='y', labelcolor=color)
ax2.plot(xs, lights, linewidth=2, color=color)
# Format timestamps to be more readable
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
fig.autofmt_xdate()
# Make sure plots stay visible or invisible as desired
ax1.collections[0].set_visible(temp_plot_visible)
ax2.get_lines()[0].set_visible(light_plot_visible)
# Dummy function prevents segfault
def _destroy(event):
pass
###############################################################################
# Main script
# Create the main window
root = tk.Tk()
root.title("Sensor Dashboard")
# Create the main container
frame = tk.Frame(root)
frame.configure(bg='white')
# Lay out the main container (expand to fit window)
frame.pack(fill=tk.BOTH, expand=1)
# Create figure for plotting
fig = figure.Figure(figsize=(2, 2))
fig.subplots_adjust(left=0.1, right=0.8)
ax1 = fig.add_subplot(1, 1, 1)
# Instantiate a new set of axes that shares the same x-axis
ax2 = ax1.twinx()
# Empty x and y lists for storing data to plot later
xs = []
temps = []
lights = []
# Variables for holding temperature and light data
temp_c = tk.DoubleVar()
lux = tk.DoubleVar()
# Create dynamic font for text
dfont = tkFont.Font(size=-24)
# Create a Tk Canvas widget out of our figure
canvas = FigureCanvasTkAgg(fig, master=frame)
canvas_plot = canvas.get_tk_widget()
# Create other supporting widgets
label_temp = tk.Label(frame, text='Temperature:', font=dfont, bg='white')
label_celsius = tk.Label(frame, textvariable=temp_c, font=dfont, bg='white')
label_unitc = tk.Label(frame, text="C", font=dfont, bg='white')
label_light = tk.Label(frame, text="Light:", font=dfont, bg='white')
label_lux = tk.Label(frame, textvariable=lux, font=dfont, bg='white')
label_unitlux = tk.Label(frame, text="lux", font=dfont, bg='white')
button_temp = tk.Button( frame,
text="Toggle Temperature",
font=dfont,
command=toggle_temp)
button_light = tk.Button( frame,
text="Toggle Light",
font=dfont,
command=toggle_light)
button_quit = tk.Button( frame,
text="Quit",
font=dfont,
command=root.destroy)
# Lay out widgets in a grid in the frame
canvas_plot.grid( row=0,
column=0,
rowspan=5,
columnspan=4,
sticky=tk.W+tk.E+tk.N+tk.S)
label_temp.grid(row=0, column=4, columnspan=2)
label_celsius.grid(row=1, column=4, sticky=tk.E)
label_unitc.grid(row=1, column=5, sticky=tk.W)
label_light.grid(row=2, column=4, columnspan=2)
label_lux.grid(row=3, column=4, sticky=tk.E)
label_unitlux.grid(row=3, column=5, sticky=tk.W)
button_temp.grid(row=5, column=0, columnspan=2)
button_light.grid(row=5, column=2, columnspan=2)
button_quit.grid(row=5, column=4, columnspan=2)
# Add a standard 5 pixel padding to all widgets
for w in frame.winfo_children():
w.grid(padx=5, pady=5)
# Make it so that the grid cells expand out to fill window
for i in range(0, 5):
frame.rowconfigure(i, weight=1)
for i in range(0, 5):
frame.columnconfigure(i, weight=1)
# Bind F11 to toggle fullscreen and ESC to end fullscreen
root.bind('<F11>', toggle_fullscreen)
root.bind('<Escape>', end_fullscreen)
# Have the resize() function be called every time the window is resized
root.bind('<Configure>', resize)
# Call empty _destroy function on exit to prevent segmentation fault
root.bind("<Destroy>", _destroy)
# Initialize our sensors
tmp102.init()
apds9301.init()
# Call animate() function periodically
fargs = (ax1, ax2, xs, temps, lights, temp_c, lux)
ani = animation.FuncAnimation( fig,
animate,
fargs=fargs,
interval=update_interval)
# Start in fullscreen mode and run
toggle_fullscreen()
root.mainloop()
Save the program (with a fun name like tkinter_dashboard.py), and run it. You should see your sensor data displayed as numerical values as well as a plot that updates once per minute.
Try pushing the “Toggle Temperature” and “Toggle Light” buttons. You should see the graph of each one disappear and reappear with each button press. This demonstrates how you can make an interactive plot using both Tkinter and Matplotlib.
You can update the update_interval variable to have the sensors polled more quickly, but it can also be fun to poll once per minute (default) and let it run for a day, as I did in my office:
If you look closely at the graph, you can see that the temperatures fell a little after 7pm, rose again, and then fell once more just before the workday started at 9am the following morning. We can surmise that the building air conditioning was running at those times to make it cooler.
Additionally, you can see that someone came into the office in the 6-7pm timeframe, as the ambient light value picked up for a short amount. Considering I did not move the sensors the next day, it looks like either more lights were on, or it was a sunnier day outside, as more light was falling on the sensor.
Code to Note:
There is a lot going on in this example, so we’ll try to cover it as succinctly as possible. Many of the concepts from the previous example, like binding key presses to trigger toggling fullscreen, are still present. Animating a graph is covered in the previous Python tutorial that introduced Matplotlib (specifically, the section about updating a graph in real time). If you are not familiar with Matplotlib, we recommend working through the following tutorial:
A few lines later, we create a Tkinter widget out of that figure:
language:python
# Create a Tk Canvas widget out of our figure
canvas = FigureCanvasTkAgg(fig, master=frame)
canvas_plot = canvas.get_tk_widget()
These lines use our imported FigureCanvasTkAgg function to take a figure and turn it into a Tkinter Canvas. We get a handle to this canvas and lay it out in our grid just like any other widget:
Instead of the periodic poll() callback that we used in the previous examples, we set up a FuncAnimation() to handle the polling and updating of the graph:
In the animate() function, we read the sensors' data (just like we did in poll()) and append it to the end of some arrays. We use this to redraw the plots on the axes (which are ultimately drawn on the Tkinter canvas widget). Note that we used .fill_between() to create the translucent red graph for temperature and a regular .plot() to create the basic blue line graph for light value.
Creating a GUI can be a good way to offer an easy-to-use interface for your customer or create a slick-looking dashboard for yourself (and your team). If you would like to learn more about Tkinter, we recommend the following resources:
Try out the various widgets and play with different layouts to get the effect you are looking for.
Note: If you think the default Tkinter widgets look outdated (hello Windows 95!), check out the ttk themed widgets package. Widgets from this set have a much more modern and updated look to them.
Looking for even more inspiration? Check out these other Raspberry Pi projects:
How much impact can the human body handle? This tutorial will teach you how to build your very own impact force monitor using a helmet, Raspberry Pi Zero, and accelerometer!
In this tutorial, we'll show you how to use the Flask framework for Python to send data from ESP8266 WiFi nodes to a Raspberry Pi over an internal WiFi network.
Experimental Products: SparkX products are rapidly produced to bring you the most cutting edge technology as it becomes available. These products are tested but come with no guarantees. Live technical support is not available for SparkX products.
The ESP32 LoRa 1-CH Gateway combines an ESP32 – a programmable microcontroller featuring both WiFi and Bluetooth radios – with an RFM95W LoRa transceiver to create a single-channel LoRa gateway. It’s a perfect, low-cost tool for monitoring a dozen-or-so LoRa devices, and relaying their messages up to the cloud.
Complete with a Qwiic connector and a breadboard-compatible array of ESP32 pin-breakouts, this board can can also serve as a general-purpose ESP32/RFM95W development platform. So, instead of using it as a LoRaWAN gateway, you can turn it into a LoRa device, and use the powerful ESP32 microcontroller to monitor sensors, host a web server, run a display or more.
These boards are a great way to begin dipping your toes into the world of LoRa and LoRaWAN. Not only can they be programmed as versatile single-channel gateway, but they can also be used as a LoRa device, or a general ESP32/RFM95W development board.
The goal of this tutorial is to get you quickly up-and-running with the ESP32 LoRa 1-CH Gateway. It’ll explain how to program the board in Arduino, how to our recommended gateway firmware, and even how to turn the board into a LoRa device.
Shopping List
The ESP32 LoRa Single-Channel Gateway is designed to be a nearly-complete LoRa gateway. There are just a few extra components you may need to get it up-and-running.
To power and program the board, you’ll need a micro-B USB cable and a computer with Arduino installed.
To boost your LoRa radio’s signal, you’ll also need an antenna. You can either use the included U.FL connector – with a U.FL-to-SMA adapter and 900 MHz SMA antenna– or solder on a ~3-inch strip of wire.
The ESP32 LoRa 1-CH Gateway includes almost everything you need to set up either a LoRaWAN gateway or device. You may not even have to solder anything to it to get started! Here’s a quick rundown of the bare minimum you’ll need to get started with the board.
Antenna
To allow the board to communicate with other LoRa devices, you’ll need to add an antenna. This can be attached to either the U.FL connector or the ANT pin on the board.
3.07" strip of 22-AWG solid-core wire soldered to the ANT pin. Use the hole adjacent to the ANT pin for strain relief!
In lieu of a U.FL antenna, a strip of wire soldered to the ANT pin and sticking straight up will work as well. Here are wire lengths for quarter-wave antennas at 915MHz and 434MHz:
Frequency
Length (inches)
Length (mm)
915 MHz
3.07" (3 + 1/16")
78mm
434 MHz
6.47" (6 + 1/2")
164mm
Powering the Board
The board is nominally powered via the on-board micro-USB connector. The other end of your USB cable can be plugged into either a computer, wall adapter, or a USB battery pack.
Alternatively, the board can be powered using a regulated 3.3V power supply. This supply should be applied to the 3.3V and GND pins.
Under normal operation the ESP32 LoRa 1-CH Gateway consumes between 50-100mA. (Running the ESP-sc-gway sketch.)
Arduino IDE Setup
To set up the gateway software you’ll need to install the ESP32 Arduino core as well as the library dependencies of the ESP32 LoRa Gateway sketch.
Arduino Board Setup
The example code and libraries for this board are all written for the Arduino IDE. If you haven’t already done so, you’ll need to install the Arduino core for ESP32. The ESP32 Arduino core must be installed manually. You can find the core files on espressif’s GitHub: https://github.com/espressif/arduino-esp32. Follow the Installation Instructions to add the core to your Arduino IDE.
Adding a Custom Board
Although it’s possible to upload code to the board using the standard board definitions, we recommend customizing the core to add support for the SparkX ESP32 LoRa Gateway.
To add the custom board, begin by downloading the board’s variant file here below.
This custom board file specifies the SPI and built-in LED pins, without it you’ll need to re-define them in your sketch.
Once the custom board has been added to the ESP32 core, open Arduino and select “SparkX ESP32 LoRa Gateway” under the Tools > Board > ESP32 Arduino menu.
This repository includes both the Arduino sketch and the libraries it depends on. Before compiling the sketch you’ll need to extract all libraries from the repository’s “library” folder into your Arduino sketchbook’s “libraries” folder. For more help installing the libraries, check out the Getting Started section of the README.
To open the example code, open the ESP-sc-gway.ino file. When the IDE loads, it should include another dozen-or-so tabs – it’s a hefty, but well-segmented sketch!
Configure the Gateway Sketch
Before uploading the ESP-1ch-Gateway sketch to your board, you’ll need to make a handful of modifications to a couple of files. Here’s a quick overview:
ESP-sc-gway.h
This file is the main source of configuration for the gateway sketch. The definitions you’ll probably have to modify are:
Radio
_LFREQ– This sets the frequency range your radio will communicate on. Set this to either 433 (Asia), 868 (EU), or 915 (US)
_SPREADING– This sets the LoRa spread factor. SF7, SF8, SF9, SF10, SF11, or SF12 can be used. Note that this will affect which devices your gateway can communicate with.
_CAD– Channel Activity Detection. If enabled (set to 1) CAD will allow the gateway to monitor messages sent at any spread factor. The tradeoff if enabled: very weak signals may not be picked up by the radio.
Hardware
_OLED– This board does not include an OLED, set this to 0.
_PIN_OUT– This configures the SPI and other hardware settings. Set this to 6, we’ll add a custom hardware definition later.
CFG_sx1276_radio– Ensure this is defined and CFG_sx1272_radio is not. This configures the LoRa radio connected to the ESP32.
The Things Network (TTN)
_TTNSERVER– The server for your LoRa router. E.g. “router.eu.thethings.network” or “us-west.thethings.network”
_TTNPORT– 1700 is the standard port for TTN
_DESCRIPTION– Customize the name of your gateway
_EMAIL– Your email address, or that of the owner of the gateway
_LAT and _LON– GPS coordinates of your gateway
WiFi
Add at least one WiFi network to the wpas wpa[] array, but leave the first entry blank. For example:
There are a lot of other values which can all optionally be configured. For a complete rundown, check out the Editing the ESP-sc-gway.h part of the README.
loramodem.h
This file defines how the LoRa modem is configured, including which frequency channels it can use and which pins the ESP32 uses to communicate with it. Be careful modifying most of the definitions in here, but one section you will have to modify is the _PIN_OUT declarations.
First, find the line that says #error "Pin Definitions _PIN_OUT must be 1(HALLARD) or 2 (COMRESULT)" and delete it. Then copy and paste these lines in its place (between the #else and #endif):
The int freqs[] array can be adjusted, if you want to use different subbands, but, beyond that, there’s not much else in here we recommend modifying.
Upload the Sketch
With your modifications made, try compiling and uploading the sketch to your ESP32. After it’s uploaded, open up your serial monitor and set the baud rate to 115200. Debug messages here can be very handy, and finding your gateway’s IP address is critical if you want to monitor the web server.
The sketch may take a long time to set up the first time through – it will format your SPIFFS file system and create a non-volatile configuration file. Once that’s complete, you should see the ESP32 attempt to connect to your WiFi network, then initialize the radio.
After the ESP32 has connected, look for it to print out an IP address. Open up your computer’s web browser and plug that into the address bar. You should be greeted by the ESP Gateway Config web portal:
Config and log page served by the ESP32.
This web page can be used to monitor messages coming through and what frequencies and spread factors they came in on. It can also be used to change your gateway’s configuration on-the-fly. You can adjust the channel or spread factor, or turn CAD on/off, or even turn on simplistic frequency-hopping.
Of course, to see any messages get through you’ll need a LoRa device (or device_s_) set up to communicate with your gateway. Check out the next section for a quick guide on setting up a second ESP32 LoRa board as a LoRa device.
Turning a Gateway Into a Device
If you have a pair of ESP32 gateway’s you can use one of them as a LoRaWAN device. This section will get you set up with our preferred LoRaWAN Arduino library and a simple example sketch to get started.
Get the Arduino-LMIC Library to Set up a device
If you have a pair of ESP32 Gateways and want to set one of them up as a LoRa device, we recommend using the Arduino-LMIC library. There seem to be many versions of the Arduino-LMIC library hanging around, we’ve had good success with the library forked by mcci-catena: https://github.com/mcci-catena/arduino-lmic.
To install the library, download the ZIP file from GitHub. Then use the Arduino’s “Add ZIP library” feature (Sketch > Include Library > Add .ZIP Library) to import the zip file into your Arduino sketchbook.
Configure LMIC
Before can use an example sketch in the LMIC library, you’ll need to configure it. To configure the library, navigate to the “arduino-lmic” library in your {Arduino Sketchbook}/libraries folder. Then go to project_config and open lmic_project_config.h.
Here you’ll define which frequency bands your LoRa device will use. You can also turn on debugging and enable/disable a host of features with definitions in this file.
Make sure you uncomment one-and-only-one of the “CFG…” declarations. If you’re in the USA, uncomment #define CFG_us915. Ultimately, this frequency should match what you set in the gateway.
Below is my config file – I’ve turned on debugging, because I’m a log junkie.
If you try to use the “raw” example in this library, you’ll need to uncomment DISABLE_INVERT_IQ_ON_RX, otherwise keep it commented-out.
Modify the “SPI.begin” call
Just to be sure your pin definitions are correct, I recommend modifying the SPI.begin line in arduino-lmic/src/hal/hal.cpp (line 136 as of this commit) to:
language:c
SPI.begin(14, 12, 13, 16);
This will ensure that your SPI pins are set correctly – this may only be necessary if you’re not using the custom “SparkX ESP32 LoRa Gateway” Arduino board.
Load the Single-Channel Device Example
We’ve taken one of the examples in the Arduino-LMIC library and modified it to work more reliably with a single-channel gateway.
Click the button below to download the example. Then open up ESP-1CH-TTN-Device-ABP.ino:
We’ve also modified the enabled channels to only use a single channel with the lines below (note this modification hasn’t yet been tested on non-US frequency bands).
language:c
// First disable all sub-bands
for (int b = 0; b < 8; ++b) {
LMIC_disableSubBand(b);
}
// Then enable the channel(s) you want to use
LMIC_enableChannel(8); // 903.9 MHz
The spread factor is set near the end of setup() with the LMIC_setDrTxpow(DR_SF7, 14); line. This can be replaced with DR_SF8, DR_SF9, or DR_SF10 at the default 903.9MHz frequency.
But wait! Before uploading this example, there’s one more modification you need to make: your LoRaWAN application, network session, and device keys! For that, and an application server, we recommend The Things Network.
Routing to The Things Network
The final components to a LoRaWAN network are a server and application. You can set up a LoRaWAN server of your own, but, for prototyping at least, The Things Network is a great, free tool for authenticating and routing your data.
Single-Channel Blues
At the trade-off of being low-cost, this gateway is only capable of monitoring a single LoRa channel on a limited set of spread factors. Single-channel gateway's don't get much support from LoRaWAN platforms like The Things Network, as they are not, necessarily, LoRaWAN-compliant. They are, however, a great way to begin exploring the world of LoRa and LoRaWAN!
If you haven’t already, head to thethingsnetwork.org and create an account. Once that’s done, go to your Console.
Create an Application
In order to create a device, you first need to create an application to house it under. In the console, click “Aplications” then click “add application”.
Fill out any ID and description you’d like. The Application EUI will automatically be generated when you create the application. You can also pick your preferred handler for the application (e.g. ttn-handler-us-west).
Create and Configure a Device
Next create a device in your application. Under the “Devices” section, click “register device”.
This is again pretty simple. Fill out a unique device ID, click the “generate” button under “Device EUI” to automatically generate a EUI. Then click “Register.”
This example sketch only supports ABP activation, so you’ll need to modify that in the device settings. In the “Device Overview” page, click “Settings”. From there, under “Activation Method” click ABP. I also recommend disabling Frame Counter Checks. The gateway is capable of frame-counter checks, but it can get out of sync – especially if you have another gateway nearby.
Save the settings and go back to your “Device Overview” page. You should see new keys, including “Network Session Key”, “App Session Key”, and “Device Address.” These will need to be plugged into the device sketch and uploaded to the device.
Update the Example Code
With your application, network session, and device keys in-hand, you’re ready to finish configuring the ESP32 LoRa device sketch and start posting data!
On the “Device Overview” page, click the “code” symbol (<>) next to “Network Session Key” and “App Session Key” – this will make the key visible and display it as a 16-byte array. Copy each of those keys, and paste them in place of the {PASTE KEY HERE} place-holders. “Network Session Key” should be pasted into the NWKSKEY variable and “App Session Key” should be pasted into the APPSKEY variable.
The DEVADDR variable expects a single 32-bit variable, so copy the “Device Address” key as shown and paste that into the PASTE_DEV_ADDR_HERE placeholder.
Here’s an example of what your three constants should look like once done:
language:c
// LoRaWAN NwkSKey, network session key
// This is the default Semtech key, which is used by the early prototype TTN
// network.
static const PROGMEM u1_t NWKSKEY[16] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF };
// LoRaWAN AppSKey, application session key
// This is the default Semtech key, which is used by the early prototype TTN
// network.
static const u1_t PROGMEM APPSKEY[16] = { 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10 };
// LoRaWAN end-device address (DevAddr)
static const u4_t DEVADDR = 0x01234567;
And that’s it! Now upload the code to your ESP32 LoRa board.
Testing the Code
After setup, the device should immediately send a “Hello, World” message. It’ll continue to send a message every minute or any time the “0” button on the board is pressed.
To check if your gateway is receiving the message, you can either check the Serial Monitor or monitor the ESP Gateway Config page served by the gateway. Every time a message is received it should be added to the “Message History” table.
If messages are getting through to your gateway, click the “Data” tab on your device to check for new messages.
The message’s paylod – which was encrypted between leaving the device and getting to the router – should be a series of hex values adding up to “Hello, world”.
Troubleshooting
If messages are not getting to your gateway, first make sure the channel and spread factor match up. Left unchanged, the device example code should be sending data out on the 903.9MHz channel at a spread factor of 7 (that’s assuming you’ve set the LMIC library to CFG_us915, if it’s set to a European frequency spectrum it’ll be 868.1MHz, SF7).
You can use the gateway’s web server to adjust these setting on the fly. Note that the channel numbers should sequentially match the freqs array in loraModem.h – e.g. channel 0 should be 903.9MHz (again, assuming US frequencies).
If messages are getting to your gateway, but not showing up on your TTN device console, consider changing the _TTNSERVER variable in “ESP-sc-gway.h”. I haven’t had success with the us-west router, but "router.eu.thethings.network" has worked perfectly (even in the US).
Resources
Thanks for coming on this single-channel LoRaWAN gateway journey with us! Here are a few links and documents that might prove handy as you continue exploring the world of LoRa with this board:
Product Repository– GitHub repository where you can find all of our latest hardware and software design files.
Let’s go over all of the things you’ll need to put your project together. Depending on what you have, you may not need everything listed here. Add it to your cart, read through the guide, and adjust the cart as necessary.
How do I install a custom Arduino library? It's easy! This tutorial will go over how to install an Arduino library using the Arduino Library Manager. For libraries not linked with the Arduino IDE, we will also go over manually installing an Arduino library.
The Qduino Mini is programmable via the Arduino IDE. If this is your first time using Arduino, please review our tutorial on installing the Arduino IDE.
If this is your first time working with the Qduino Mini, you may need to add drivers and the board add-on through the boards manager. Please visit the Qduino Mini quick start guide for detailed instructions on installing drivers and programming a Qduino Mini via the Arduino IDE.
Note: This example assumes you are using the latest version of the Arduino IDE on your desktop. If this is your first time using Arduino, please review our tutorial on installing the Arduino IDE. If you have not previously installed an Arduino library, please check out our installation guide.
In this program, we will also be utilizing the Adafruit Neopixel Library. You can obtain this library through the Arduino Library Manager. Search for Adafruit Neopixel and you should be able to install the latest version of the library. If you prefer downloading the library manually you can grab it from the GitHub repository:
We have provided the code for this project below. Copy and paste it into your Arduino IDE and then upload it to your board. Make sure you have the correct board selected in the boards manager as well as the port under the port drop down.
language:c
/******************************************************************************
lightsculpture.ino
Melissa Felderman @ SparkFun Electronics
creation date: July 31, 2018
Resources:
Adafruit_NeoPixel.h - Adafruit Neopixel library and example functions
*****************************************************************************/
#include <Adafruit_NeoPixel.h> //include afafruit library
#define PIN 6 //LED matrix pin
#define brightPot A0 //potentiometer to controll brightness
#define pwrSwitch 4 //power switch
#define momBut 5 //button to control LED mode
int numPix = 64; //total LED count
int brightPotVal; //Variable to hold pot value
int pixelBrightness; //variabe to hold brightness value
int switchState; //variable to hold switch value
int butState; //variable to hold button value
int mode = 0; //starting mode for switch state
int prevButState = LOW;
boolean butBool = false;
int topMode = 4; //max number of LED modes in switch state
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 200;
Adafruit_NeoPixel strip = Adafruit_NeoPixel(numPix, PIN, NEO_GRB + NEO_KHZ800); //declare neopixel matrix
//create an array for each row of LEDs
int rowOne[] = {0, 1, 2, 3, 4, 5, 6, 7};
int rowTwo[] = {8, 9, 10, 11, 12, 13, 14, 15};
int rowThree[] = {16, 17, 18, 19, 20, 21, 22, 23};
int rowFour[] = {24, 25, 26, 27, 28, 29, 30, 31};
int rowFive[] = {32, 33, 34, 35, 36, 37, 38, 39};
int rowSix[] = {40, 41, 42, 43, 44, 45, 46, 47};
int rowSeven[] = {48, 49, 50, 51, 52, 53, 54, 55};
int rowEight[] = {56, 57, 58, 59, 60, 61, 62, 63};
void setup() {
Serial.begin(9600);
strip.begin();
strip.show();
pinMode(momBut, INPUT);
pinMode(pwrSwitch, INPUT);
}
void loop() {
brightPotVal = analogRead(brightPot);
pixelBrightness = map(brightPotVal, 0, 1023, 0, 200);
switchState = digitalRead(pwrSwitch);
butState = digitalRead(momBut);
strip.setBrightness(pixelBrightness);
strip.show();
//function to debounce button
if ((millis() - lastDebounceTime) > debounceDelay) {
if ((butState == HIGH) && (butBool == false)) {
butBool = true;
mode++;
lastDebounceTime = millis();
} butBool = false;
} if (mode > topMode) {
mode = 0;
}
Serial.println(mode);
//switch state function to cycle through modes on LEDs, you can add as many or as few as you would like
if (switchState == HIGH) {
switch ( mode ) {
case 0:
for (int i = 0; i < numPix; i++) {
strip.setPixelColor(i, 255, 255, 255);
}
strip.show();
break;
case 1:
rainbow();
break;
case 2:
buleGreenGradient();
break;
case 3:
pinkGradient();
break;
case 4:
yellowGradient();
break;
}
} else if (switchState == LOW) {
for (int i = 0; i < numPix; i++) {
strip.setPixelColor(i, 0, 0, 0);
}
strip.show();
}
}
//functions for LED colors
void everyOther() {
for (int i = 0; i < 8; i++) {
strip.setPixelColor(rowOne[i], 255, 255, 255);
strip.setPixelColor(rowThree[i], 255, 255, 255);
strip.setPixelColor(rowFive[i], 255, 255, 255);
strip.setPixelColor(rowSeven[i], 255, 255, 255);
strip.setPixelColor(rowTwo[i], 0, 0, 0);
strip.setPixelColor(rowFour[i], 0, 0, 0);
strip.setPixelColor(rowSix[i], 0, 0, 0);
strip.setPixelColor(rowEight[i], 0, 0, 0);
}
strip.show();
}
void pinkGradient() {
for (int i = 0; i < 8; i++) {
strip.setPixelColor(rowOne[i], 185, 0, 255);
strip.setPixelColor(rowTwo[i], 195, 0, 230);
strip.setPixelColor(rowThree[i], 205, 0, 200);
strip.setPixelColor(rowFour[i], 215, 0, 160);
strip.setPixelColor(rowFive[i], 225, 0, 120);
strip.setPixelColor(rowSix[i], 235, 0, 80);
strip.setPixelColor(rowSeven[i], 245, 0, 40);
strip.setPixelColor(rowEight[i], 255, 0, 10);
}
strip.show();
}
void buleGreenGradient() {
for (int i = 0; i < 8; i++) {
strip.setPixelColor(rowOne[i], 0, 75, 255);
strip.setPixelColor(rowTwo[i], 0, 100, 225);
strip.setPixelColor(rowThree[i], 0, 125, 200);
strip.setPixelColor(rowFour[i], 00, 150, 175);
strip.setPixelColor(rowFive[i], 0, 175, 150);
strip.setPixelColor(rowSix[i], 0, 200, 125);
strip.setPixelColor(rowSeven[i], 0, 225, 100);
strip.setPixelColor(rowEight[i], 0, 255, 75);
}
strip.show();
}
void yellowGradient() {
for (int i = 0; i < 8; i++) {
strip.setPixelColor(rowOne[i], 255, 255, 25);
strip.setPixelColor(rowTwo[i], 255, 220, 25);
strip.setPixelColor(rowThree[i], 255, 190, 25);
strip.setPixelColor(rowFour[i], 255, 160, 25);
strip.setPixelColor(rowFive[i], 255, 130, 25);
strip.setPixelColor(rowSix[i], 255, 100, 25);
strip.setPixelColor(rowSeven[i], 255, 70, 25);
strip.setPixelColor(rowEight[i], 255, 40, 25);
}
strip.show();
}
void rainbow() {
for (int i = 0; i < 8; i++) {
strip.setPixelColor(rowOne[i], 255, 0, 0);
} for (int i = 0; i < 8; i++) {
strip.setPixelColor(rowTwo[i], 255, 100, 0);
} for (int i = 0; i < 8; i++) {
strip.setPixelColor(rowThree[i], 255, 255, 0);
} for (int i = 0; i < 8; i++) {
strip.setPixelColor(rowFour[i], 0, 255, 0);
} for (int i = 0; i < 8; i++) {
strip.setPixelColor(rowFive[i], 0, 255, 200);
} for (int i = 0; i < 8; i++) {
strip.setPixelColor(rowSix[i], 0, 0, 255);
} for (int i = 0; i < 8; i++) {
strip.setPixelColor(rowSeven[i], 255, 0, 255);
} for (int i = 0; i < 8; i++) {
strip.setPixelColor(rowEight[i], 255, 0, 130);
}
strip.show();
}
Understanding Your Circuit
Inside the light sculpture enclosure is one NeoPixel NeoMatrix 8x8 - 64 RGB LED containing a total of 64 addressable WS2812 LEDs, a 100uF Capacitor to protect the first LED, one Qduino Mini - Arduino Dev Board to act as the brains of the project, a Mini Power Switch to easily turn the project on or off, a Tactile Button to navigate between light modes, and a Potentiometer to control brightness. A small piece of Snappable Protoboard is used to extend the ‘+’ and ‘-’ terminals on the Qduino Mini making it easier to connect the ‘+’ and ‘-’ leads from your components. A MicroB USB cable is used to supply wall power directly to the USB port on the Qduino, but a large LiPo battery would work as well.
⚡ Please note! The Qduino mini runs on 3.3V!
As shown in the circuit diagram below, the Qduino Mini is the brains of this project. Pin D6 is connected to the NeoPixel NeoMatrix, the potentiometer is connect to pin A0, the switch to pin D4, and the momentary pushbutton to D5. The first LED on the NeoPixel Matrix is protected using a 100uF capacitor between ‘+’ and ‘-’ on the matrix and ‘+’ and ‘-’ on the Qduino Mini. You may also notice that while the rest of the components directly connect to ‘+’ on the Qduini Mini, the switch and button both connect to ‘-’ of the Qduino via a resistor. This is called a pulldown resistor and allows the Qduino to get accurate readings of HIGH and LOW. To learn more about how to use pull up and pull down resistors with Arduino, check out our tutorial.
Having a hard time seeing the circuit? Click on the wiring diagram for a closer look.
Enclosure Fabrication
The first part of this project is printing the enclosure on a 3D printer. If you do not have access to a 3D printer, check with your local library or maker space. There are also 3D printing services which you can use online like Shapeways.
Download 3D Printer Drivers
Download any drivers and firmware needed to control your 3D printer. If you are working with a LulzBot like the ones sold through SparkFun, check out their downloads page in the support section of their website. If you are working with a different printer, check out the printer brand’s website for information on drivers and firmware.
Download Project File
Download the .stl file from the project page on thingiverse.
Prepare your Gcode by loading the .stl into your driver software. Either save the Gcode to an SD card or prepare to print by connecting your computer to the printer via USB. Make sure your settings match the material you plan to use. I recommend using black filament because it is effective in blocking light. A lighter color may leak light. If you prefer a light colored enclosure, I would print it in black and then spray paint it afterwards before adding the electronics.
Heads up! To save time and filament, flip the enclosure over in your driver so that the horizontal slots are flat against the bed.
Print
Print the enclosure!
Putting Your Electronics Together
Now that we have printed the enclosure, let’s prepare the electronics for our circuit.
PLEASE NOTE! Always test your circuit on a breadboard before soldering it together in the enclosure.
Solder Wire Leads
Solder wire leads of about 2" to your components using solid core hook up wire. To make things easier for yourself later, use red wire for ‘+’, black from GND, and white for GPIO input/output. Use heat shrink to secure and isolate your connections on the button and switch.
Click the image for a closer look.
Place Electronics
Place the electronics in their respective spaces in the enclosure. For the NeoPixel matrix, make sure your DIN pin is in the opposite corner of the potentiometer. This is to ensure your LED patterns align with the slots. You can secure the potentiometer in place with the nut that comes with it.
For the button and switch, use a small dab of hot glue on the backside to hold them in place.
Solder a ‘+’ and ‘-’ lead to the VCC and GND of your Qduino respectively. There is only one ‘+’ and one ‘-’ pin on the Qduino so we will need to extend these two pins in order for your components to connect. To do this, grab a small piece of protoboard and solder the opposite end of the ‘+’ lead to one corner and the opposite end of the ‘-’ lead to the opposite corner.
Plug the USB cable into the Qduino and place it face down behind the potentiometer with the USB cable threaded through the tab in the back of the enclosure.
Before you begin soldering your circuit together, all parts in the enclosure should look like this:
Solder Circuits
Solder your circuit together according to the fritzing diagram provided above. Use the ‘+’ and ‘-’ extensions on the protoboard for all of your ‘+’ and ‘-’ leads. Don't forget to solder a resistor between the GND extension and the the GND leads on your switch and button. It is also best practice to use a capacitor between the ‘+’ and ‘-’ leads on your NeoPixel matrix and the ‘+’ and ‘-’ extensions to protect the first LED from a rush of current.
Verify
Test your circuit. Turn it on and make sure it is working according to the program.
Laser Cutting Your Design
Now that we have the base enclosure completed with the electronics soldered together into a circuit, let’s take a look at how to add a decorative flair to your project.
Download Templates
Download the laser cutter template from the project thingiverse page to prepare to cut your acrylic inserts. Open this with illustrator and begin to design your etching and/or cuts. I have found that the light is picked up by both the etched design and the edges of the plastic, so you can use both of these elements to create your final design. There are 8 rows of LEDs on your matrix so you will want to make 8 different acrylic inserts.
Cut Your Designs
These inserts were cut and the designs rastered on our Epilog 75W laser cutter according to the manufacturer’s specifications. If you do not have access to a laser cutter, check out your local library or hackerspace. Alternatively, you can order your designs online at Ponoko.
Light up your Life!
Pop the inserts into your enclosure and enjoy!
Resources and Going Further
For more information related to this tutorial, check out the following links:
Make bright, colorful displays using the 32x16, 32x32, and 32x64 RGB LED matrix panels. This hookup guide shows how to hook up these panels and control them with an Arduino.
Embedded with heating pads and four Neopixel rings, these earmuffs do more than your average winter accessory to keep you warm while still looking good.